@odoo/o-spreadsheet 17.3.0-alpha.9 → 17.3.0

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.9
7
- * @date 2024-05-24T11:32:11.976Z
8
- * @hash aac246d
6
+ * @version 17.3.0
7
+ * @date 2024-05-31T14:21:26.547Z
8
+ * @hash 271202e
9
9
  */
10
10
 
11
11
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -40,6 +40,8 @@ const COMPOSER_ASSISTANT_COLOR = "#9B359B";
40
40
  const CHART_WATERFALL_POSITIVE_COLOR = "#006FBE";
41
41
  const CHART_WATERFALL_NEGATIVE_COLOR = "#E40000";
42
42
  const CHART_WATERFALL_SUBTOTAL_COLOR = "#AAAAAA";
43
+ const DEFAULT_CHART_PADDING = 20;
44
+ const DEFAULT_CHART_FONT_SIZE = 22;
43
45
  // Color picker defaults as upper case HEX to match `toHex`helper
44
46
  const COLOR_PICKER_DEFAULTS = [
45
47
  "#000000",
@@ -1088,6 +1090,44 @@ function darkenColor(color, percentage) {
1088
1090
  hsla.l = hsla.l - percentage * hsla.l;
1089
1091
  return hslaToHex(hsla);
1090
1092
  }
1093
+ const ColorsList = [
1094
+ // the same colors as those used in odoo reporting
1095
+ "rgb(31,119,180)",
1096
+ "rgb(255,127,14)",
1097
+ "rgb(174,199,232)",
1098
+ "rgb(255,187,120)",
1099
+ "rgb(44,160,44)",
1100
+ "rgb(152,223,138)",
1101
+ "rgb(214,39,40)",
1102
+ "rgb(255,152,150)",
1103
+ "rgb(148,103,189)",
1104
+ "rgb(197,176,213)",
1105
+ "rgb(140,86,75)",
1106
+ "rgb(196,156,148)",
1107
+ "rgb(227,119,194)",
1108
+ "rgb(247,182,210)",
1109
+ "rgb(127,127,127)",
1110
+ "rgb(199,199,199)",
1111
+ "rgb(188,189,34)",
1112
+ "rgb(219,219,141)",
1113
+ "rgb(23,190,207)",
1114
+ "rgb(158,218,229)",
1115
+ ];
1116
+ function getNthColor(index) {
1117
+ return ColorsList[index % ColorsList.length];
1118
+ }
1119
+ class ColorGenerator {
1120
+ currentColorIndex = 0;
1121
+ colors;
1122
+ constructor(colors = []) {
1123
+ this.colors = colors;
1124
+ }
1125
+ next() {
1126
+ return this.colors?.[this.currentColorIndex]
1127
+ ? this.colors[this.currentColorIndex++]
1128
+ : getNthColor(this.currentColorIndex++);
1129
+ }
1130
+ }
1091
1131
 
1092
1132
  //------------------------------------------------------------------------------
1093
1133
  // Coordinate
@@ -1933,11 +1973,13 @@ const invalidateEvaluationCommands = new Set([
1933
1973
  "RENAME_SHEET",
1934
1974
  "DELETE_SHEET",
1935
1975
  "CREATE_SHEET",
1976
+ "DUPLICATE_SHEET",
1936
1977
  "ADD_COLUMNS_ROWS",
1937
1978
  "REMOVE_COLUMNS_ROWS",
1938
1979
  "UNDO",
1939
1980
  "REDO",
1940
1981
  "ADD_MERGE",
1982
+ "REMOVE_MERGE",
1941
1983
  "UPDATE_LOCALE",
1942
1984
  "ADD_PIVOT",
1943
1985
  "UPDATE_PIVOT",
@@ -4876,18 +4918,22 @@ function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
4876
4918
  /**
4877
4919
  * Create a range from a xc. If the xc is empty, this function returns undefined.
4878
4920
  */
4879
- function createRange(getters, sheetId, range) {
4880
- return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
4921
+ function createValidRange(getters, sheetId, xc) {
4922
+ if (!xc)
4923
+ return;
4924
+ const range = getters.getRangeFromSheetXC(sheetId, xc);
4925
+ return !(range.invalidSheetName || range.invalidXc) ? range : undefined;
4881
4926
  }
4882
4927
  /**
4883
4928
  * Spread multiple colrows zone to one row/col zone and add a many new input range as needed.
4884
4929
  * For example, A1:B4 will become [A1:A4, B1:B4]
4885
4930
  */
4886
- function spreadRange(getters, ranges) {
4931
+ function spreadRange(getters, dataSets) {
4887
4932
  const postProcessedRanges = [];
4888
- for (const range of ranges) {
4933
+ for (const dataSet of dataSets) {
4934
+ const range = dataSet.dataRange;
4889
4935
  if (!getters.isRangeValid(range)) {
4890
- postProcessedRanges.push(range); // ignore invalid range
4936
+ postProcessedRanges.push(dataSet); // ignore invalid range
4891
4937
  continue;
4892
4938
  }
4893
4939
  const { sheetName } = splitReference(range);
@@ -4896,27 +4942,33 @@ function spreadRange(getters, ranges) {
4896
4942
  if (zone.bottom !== zone.top && zone.left != zone.right) {
4897
4943
  if (zone.right) {
4898
4944
  for (let j = zone.left; j <= zone.right; ++j) {
4899
- postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
4900
- left: j,
4901
- right: j,
4902
- top: zone.top,
4903
- bottom: zone.bottom,
4904
- })}`);
4945
+ postProcessedRanges.push({
4946
+ ...dataSet,
4947
+ dataRange: `${sheetPrefix}${zoneToXc({
4948
+ left: j,
4949
+ right: j,
4950
+ top: zone.top,
4951
+ bottom: zone.bottom,
4952
+ })}`,
4953
+ });
4905
4954
  }
4906
4955
  }
4907
4956
  else {
4908
4957
  for (let j = zone.top; j <= zone.bottom; ++j) {
4909
- postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
4910
- left: zone.left,
4911
- right: zone.right,
4912
- top: j,
4913
- bottom: j,
4914
- })}`);
4958
+ postProcessedRanges.push({
4959
+ ...dataSet,
4960
+ dataRange: `${sheetPrefix}${zoneToXc({
4961
+ left: zone.left,
4962
+ right: zone.right,
4963
+ top: j,
4964
+ bottom: j,
4965
+ })}`,
4966
+ });
4915
4967
  }
4916
4968
  }
4917
4969
  }
4918
4970
  else {
4919
- postProcessedRanges.push(range);
4971
+ postProcessedRanges.push(dataSet);
4920
4972
  }
4921
4973
  }
4922
4974
  return postProcessedRanges;
@@ -5035,6 +5087,11 @@ function getDefaultCellHeight(ctx, cell, colSize) {
5035
5087
  const fontSize = computeTextFontSizeInPixels(cell.style);
5036
5088
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
5037
5089
  }
5090
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
5091
+ const italicStr = italic ? "italic" : "";
5092
+ const weight = bold ? "bold" : "";
5093
+ return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
5094
+ }
5038
5095
  const textWidthCache = {};
5039
5096
  function computeTextWidth(context, text, style, fontUnit = "pt") {
5040
5097
  const font = computeTextFont(style, fontUnit);
@@ -5055,6 +5112,28 @@ function computeCachedTextWidth(context, text) {
5055
5112
  }
5056
5113
  return textWidthCache[font][text];
5057
5114
  }
5115
+ const textDimensionsCache = {};
5116
+ function computeTextDimension(context, text, style, fontUnit = "pt") {
5117
+ const font = computeTextFont(style, fontUnit);
5118
+ context.save();
5119
+ context.font = font;
5120
+ const dimensions = computeCachedTextDimension(context, text);
5121
+ context.restore();
5122
+ return dimensions;
5123
+ }
5124
+ function computeCachedTextDimension(context, text) {
5125
+ const font = context.font;
5126
+ if (!textDimensionsCache[font]) {
5127
+ textDimensionsCache[font] = {};
5128
+ }
5129
+ if (textDimensionsCache[font][text] === undefined) {
5130
+ const measure = context.measureText(text);
5131
+ const width = measure.width;
5132
+ const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
5133
+ textDimensionsCache[font][text] = { width, height };
5134
+ }
5135
+ return textDimensionsCache[font][text];
5136
+ }
5058
5137
  function fontSizeInPixels(fontSize) {
5059
5138
  return Math.round((fontSize * 96) / 72);
5060
5139
  }
@@ -8824,7 +8903,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8824
8903
  }
8825
8904
  }
8826
8905
  if (!highlight.noFill) {
8827
- ctx.fillStyle = setColorAlpha(color, highlight.fillAlpha ?? 0.12);
8906
+ ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8828
8907
  ctx.fillRect(x, y, width, height);
8829
8908
  }
8830
8909
  }
@@ -8873,7 +8952,32 @@ class HighlightStore extends SpreadsheetStore {
8873
8952
  }
8874
8953
  }
8875
8954
 
8876
- const NotificationStore = createAbstractStore("Notifications");
8955
+ class NotificationStore {
8956
+ mutators = [
8957
+ "notifyUser",
8958
+ "raiseError",
8959
+ "askConfirmation",
8960
+ "updateNotificationCallbacks",
8961
+ ];
8962
+ notifyUser = (notification) => window.alert(notification);
8963
+ askConfirmation = (content, confirm, cancel) => {
8964
+ if (window.confirm(content)) {
8965
+ confirm();
8966
+ }
8967
+ else {
8968
+ cancel?.();
8969
+ }
8970
+ };
8971
+ raiseError = (text, callback) => {
8972
+ window.alert(text);
8973
+ callback?.();
8974
+ };
8975
+ updateNotificationCallbacks(methods) {
8976
+ this.notifyUser = methods.notifyUser || this.notifyUser;
8977
+ this.raiseError = methods.raiseError || this.raiseError;
8978
+ this.askConfirmation = methods.askConfirmation || this.askConfirmation;
8979
+ }
8980
+ }
8877
8981
 
8878
8982
  const CELL_DELETED_MESSAGE = _t("The cell you are trying to edit has been deleted.");
8879
8983
  class ComposerStore extends SpreadsheetStore {
@@ -9894,10 +9998,10 @@ function adaptChartRange(range, applyChange) {
9894
9998
  /**
9895
9999
  * Create the dataSet objects from xcs
9896
10000
  */
9897
- function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
10001
+ function createDataSets(getters, customizedDataSets, sheetId, dataSetsHaveTitle) {
9898
10002
  const dataSets = [];
9899
- for (const sheetXC of dataSetsString) {
9900
- const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
10003
+ for (const dataSet of customizedDataSets) {
10004
+ const dataRange = getters.getRangeFromSheetXC(sheetId, dataSet.dataRange);
9901
10005
  const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
9902
10006
  if (invalidSheetName || invalidXc) {
9903
10007
  continue;
@@ -9914,26 +10018,36 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
9914
10018
  left: column,
9915
10019
  right: column,
9916
10020
  };
9917
- dataSets.push(createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
9918
- ? {
9919
- top: columnZone.top,
9920
- bottom: columnZone.top,
9921
- left: columnZone.left,
9922
- right: columnZone.left,
9923
- }
9924
- : undefined));
10021
+ dataSets.push({
10022
+ ...createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
10023
+ ? {
10024
+ top: columnZone.top,
10025
+ bottom: columnZone.top,
10026
+ left: columnZone.left,
10027
+ right: columnZone.left,
10028
+ }
10029
+ : undefined),
10030
+ backgroundColor: dataSet.backgroundColor,
10031
+ rightYAxis: dataSet.yAxisId === "y1",
10032
+ customLabel: dataSet.label,
10033
+ });
9925
10034
  }
9926
10035
  }
9927
10036
  else {
9928
10037
  /* 1 cell, 1 row or 1 column */
9929
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
9930
- ? {
9931
- top: zone.top,
9932
- bottom: zone.top,
9933
- left: zone.left,
9934
- right: zone.left,
9935
- }
9936
- : undefined));
10038
+ dataSets.push({
10039
+ ...createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
10040
+ ? {
10041
+ top: zone.top,
10042
+ bottom: zone.top,
10043
+ left: zone.left,
10044
+ right: zone.left,
10045
+ }
10046
+ : undefined),
10047
+ backgroundColor: dataSet.backgroundColor,
10048
+ rightYAxis: dataSet.yAxisId === "y1",
10049
+ customLabel: dataSet.label,
10050
+ });
9937
10051
  }
9938
10052
  }
9939
10053
  return dataSets;
@@ -9973,11 +10087,24 @@ function toExcelDataset(getters, ds) {
9973
10087
  }
9974
10088
  }
9975
10089
  const dataRange = ds.dataRange.clone({ zone: dataZone });
10090
+ let label = {};
10091
+ if (ds.customLabel) {
10092
+ label = {
10093
+ text: ds.customLabel,
10094
+ };
10095
+ }
10096
+ else if (ds.labelCell) {
10097
+ label = {
10098
+ reference: getters.getRangeString(ds.labelCell, "forceSheetReference", {
10099
+ useFixedReference: true,
10100
+ }),
10101
+ };
10102
+ }
9976
10103
  return {
9977
- label: ds.labelCell
9978
- ? getters.getRangeString(ds.labelCell, "forceSheetReference", { useFixedReference: true })
9979
- : undefined,
10104
+ label,
9980
10105
  range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }),
10106
+ backgroundColor: ds.backgroundColor,
10107
+ rightYAxis: ds.rightYAxis,
9981
10108
  };
9982
10109
  }
9983
10110
  function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
@@ -10003,45 +10130,16 @@ function transformChartDefinitionWithDataSetsWithZone(definition, executed) {
10003
10130
  labelRange = labelZone ? zoneToXc(labelZone) : undefined;
10004
10131
  }
10005
10132
  const dataSets = definition.dataSets
10006
- .map(toUnboundedZone)
10133
+ .map((ds) => toUnboundedZone(ds.dataRange))
10007
10134
  .map((zone) => transformZone(zone, executed))
10008
10135
  .filter(isDefined)
10009
- .map(zoneToXc);
10136
+ .map((xc) => ({ dataRange: zoneToXc(xc) }));
10010
10137
  return {
10011
10138
  ...definition,
10012
10139
  labelRange,
10013
10140
  dataSets,
10014
10141
  };
10015
10142
  }
10016
- const GraphColors = [
10017
- // the same colors as those used in odoo reporting
10018
- "rgb(31,119,180)",
10019
- "rgb(255,127,14)",
10020
- "rgb(174,199,232)",
10021
- "rgb(255,187,120)",
10022
- "rgb(44,160,44)",
10023
- "rgb(152,223,138)",
10024
- "rgb(214,39,40)",
10025
- "rgb(255,152,150)",
10026
- "rgb(148,103,189)",
10027
- "rgb(197,176,213)",
10028
- "rgb(140,86,75)",
10029
- "rgb(196,156,148)",
10030
- "rgb(227,119,194)",
10031
- "rgb(247,182,210)",
10032
- "rgb(127,127,127)",
10033
- "rgb(199,199,199)",
10034
- "rgb(188,189,34)",
10035
- "rgb(219,219,141)",
10036
- "rgb(23,190,207)",
10037
- "rgb(158,218,229)",
10038
- ];
10039
- class ChartColors {
10040
- graphColorIndex = 0;
10041
- next() {
10042
- return GraphColors[this.graphColorIndex++ % GraphColors.length];
10043
- }
10044
- }
10045
10143
  /**
10046
10144
  * Choose a font color based on a background color.
10047
10145
  * The font is white with a dark background.
@@ -10054,11 +10152,11 @@ function chartFontColor(backgroundColor) {
10054
10152
  }
10055
10153
  function checkDataset(definition) {
10056
10154
  if (definition.dataSets) {
10057
- const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined;
10155
+ const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range.dataRange)) !== undefined;
10058
10156
  if (invalidRanges) {
10059
10157
  return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
10060
10158
  }
10061
- const zones = definition.dataSets.map(toUnboundedZone);
10159
+ const zones = definition.dataSets.map((ds) => toUnboundedZone(ds.dataRange));
10062
10160
  if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) {
10063
10161
  return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
10064
10162
  }
@@ -10098,6 +10196,35 @@ function getChartPositionAtCenterOfViewport(getters, chartSize) {
10098
10196
  }; // Position at the center of the scrollable viewport
10099
10197
  return position;
10100
10198
  }
10199
+ function getChartAxisTitleRuntime(design) {
10200
+ if (design?.title?.text) {
10201
+ const { text, color, align, italic, bold } = design.title;
10202
+ return {
10203
+ display: true,
10204
+ text,
10205
+ color,
10206
+ font: {
10207
+ style: italic ? "italic" : "normal",
10208
+ weight: bold ? "bold" : "normal",
10209
+ },
10210
+ align: align === "left" ? "start" : align === "right" ? "end" : "center",
10211
+ };
10212
+ }
10213
+ return;
10214
+ }
10215
+ function getDefinedAxis(definition) {
10216
+ let useLeftAxis = false, useRightAxis = false;
10217
+ for (const design of definition.dataSets || []) {
10218
+ if (design.yAxisId === "y1") {
10219
+ useRightAxis = true;
10220
+ }
10221
+ else {
10222
+ useLeftAxis = true;
10223
+ }
10224
+ }
10225
+ useLeftAxis ||= !useRightAxis;
10226
+ return { useLeftAxis, useRightAxis };
10227
+ }
10101
10228
 
10102
10229
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
10103
10230
  if (!baseline) {
@@ -10199,8 +10326,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10199
10326
  type = "scorecard";
10200
10327
  constructor(definition, sheetId, getters) {
10201
10328
  super(definition, sheetId, getters);
10202
- this.keyValue = createRange(getters, sheetId, definition.keyValue);
10203
- this.baseline = createRange(getters, sheetId, definition.baseline);
10329
+ this.keyValue = createValidRange(getters, sheetId, definition.keyValue);
10330
+ this.baseline = createValidRange(getters, sheetId, definition.baseline);
10204
10331
  this.baselineMode = definition.baselineMode;
10205
10332
  this.baselineDescr = definition.baselineDescr;
10206
10333
  this.background = definition.background;
@@ -10215,8 +10342,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10215
10342
  return {
10216
10343
  background: context.background,
10217
10344
  type: "scorecard",
10218
- keyValue: context.range ? context.range[0] : undefined,
10219
- title: context.title || "",
10345
+ keyValue: context.range ? context.range[0].dataRange : undefined,
10346
+ title: context.title || { text: "" },
10220
10347
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
10221
10348
  baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
10222
10349
  baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
@@ -10254,7 +10381,9 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10254
10381
  getContextCreation() {
10255
10382
  return {
10256
10383
  ...this,
10257
- range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,
10384
+ range: this.keyValue
10385
+ ? [{ dataRange: this.getters.getRangeString(this.keyValue, this.sheetId) }]
10386
+ : undefined,
10258
10387
  auxiliaryRange: this.baseline
10259
10388
  ? this.getters.getRangeString(this.baseline, this.sheetId)
10260
10389
  : undefined,
@@ -10301,7 +10430,10 @@ function drawScoreChart(structure, canvas) {
10301
10430
  if (structure.title) {
10302
10431
  ctx.font = structure.title.style.font;
10303
10432
  ctx.fillStyle = structure.title.style.color;
10433
+ const baseline = ctx.textBaseline;
10434
+ ctx.textBaseline = "middle";
10304
10435
  ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
10436
+ ctx.textBaseline = baseline;
10305
10437
  }
10306
10438
  if (structure.baseline) {
10307
10439
  ctx.font = structure.baseline.style.font;
@@ -10388,7 +10520,10 @@ function createScorecardChartRuntime(chart, getters) {
10388
10520
  ? toNumber(baselineDisplay, locale)
10389
10521
  : 0;
10390
10522
  return {
10391
- title: _t(chart.title),
10523
+ title: {
10524
+ ...chart.title,
10525
+ text: _t(chart.title.text ?? ""),
10526
+ },
10392
10527
  keyValue: formattedKeyValue,
10393
10528
  baselineDisplay,
10394
10529
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
@@ -10420,10 +10555,9 @@ function createScorecardChartRuntime(chart, getters) {
10420
10555
  }
10421
10556
 
10422
10557
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
10423
- const TITLE_FONT_SIZE = 18;
10424
10558
  const KEY_BOX_HEIGHT_RATIO = 0.8;
10425
10559
  /* Padding at the border of the chart */
10426
- const CHART_PADDING = 10;
10560
+ const CHART_PADDING = DEFAULT_CHART_PADDING;
10427
10561
  const BOTTOM_PADDING_RATIO = 0.05;
10428
10562
  /**
10429
10563
  * Line height (in em)
@@ -10434,11 +10568,6 @@ function formatBaselineDescr(baselineDescr, baseline) {
10434
10568
  const _baselineDescr = baselineDescr || "";
10435
10569
  return baseline && _baselineDescr ? " " + _baselineDescr : _baselineDescr;
10436
10570
  }
10437
- function getDefaultContextFont(fontSize, bold = false, italic = false) {
10438
- const italicStr = italic ? "italic" : "";
10439
- const weight = bold ? "bold" : "";
10440
- return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
10441
- }
10442
10571
  function getScorecardConfiguration({ width, height }, runtime) {
10443
10572
  const designer = new ScorecardChartConfigBuilder({ width, height }, runtime);
10444
10573
  return designer.computeDesign();
@@ -10466,13 +10595,25 @@ class ScorecardChartConfigBuilder {
10466
10595
  const style = this.getTextStyles();
10467
10596
  let titleHeight = 0;
10468
10597
  if (this.title) {
10469
- ({ height: titleHeight } = this.getFullTextDimensions(this.title, style.title.font));
10598
+ let x, titleWidth;
10599
+ ({ height: titleHeight, width: titleWidth } = this.getFullTextDimensions(this.title, style.title.font));
10600
+ switch (this.runtime.title.align) {
10601
+ case "center":
10602
+ x = (this.width - titleWidth) / 2;
10603
+ break;
10604
+ case "right":
10605
+ x = this.width - titleWidth - CHART_PADDING;
10606
+ break;
10607
+ case "left":
10608
+ default:
10609
+ x = CHART_PADDING;
10610
+ }
10470
10611
  structure.title = {
10471
10612
  text: this.title,
10472
10613
  style: style.title,
10473
10614
  position: {
10474
- x: CHART_PADDING,
10475
- y: CHART_PADDING / 2 + titleHeight,
10615
+ x,
10616
+ y: CHART_PADDING + titleHeight / 2,
10476
10617
  },
10477
10618
  };
10478
10619
  }
@@ -10573,7 +10714,7 @@ class ScorecardChartConfigBuilder {
10573
10714
  return structure;
10574
10715
  }
10575
10716
  get title() {
10576
- return this.runtime.title;
10717
+ return this.runtime.title.text ?? "";
10577
10718
  }
10578
10719
  get keyValue() {
10579
10720
  return this.runtime.keyValue;
@@ -10641,8 +10782,8 @@ class ScorecardChartConfigBuilder {
10641
10782
  }
10642
10783
  return {
10643
10784
  title: {
10644
- font: getDefaultContextFont(TITLE_FONT_SIZE),
10645
- color: this.secondaryFontColor,
10785
+ font: getDefaultContextFont(DEFAULT_CHART_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10786
+ color: this.runtime.title.color ?? this.secondaryFontColor,
10646
10787
  },
10647
10788
  keyValue: {
10648
10789
  color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
@@ -10675,7 +10816,7 @@ class ScorecardChartConfigBuilder {
10675
10816
  getDrawableHeight() {
10676
10817
  const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10677
10818
  let availableHeight = this.height - 2 * verticalPadding;
10678
- availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
10819
+ availableHeight -= this.title ? DEFAULT_CHART_FONT_SIZE * LINE_HEIGHT : 0;
10679
10820
  return availableHeight;
10680
10821
  }
10681
10822
  }
@@ -12025,7 +12166,7 @@ const COUNTUNIQUEIFS = {
12025
12166
  compute: function (range, ...args) {
12026
12167
  let uniqueValues = new Set();
12027
12168
  visitMatchingRanges(args, (i, j) => {
12028
- const data = range[i][j];
12169
+ const data = range[i]?.[j];
12029
12170
  if (isDataNonEmpty(data)) {
12030
12171
  uniqueValues.add(data.value);
12031
12172
  }
@@ -12655,7 +12796,7 @@ const SUMIF = {
12655
12796
  }
12656
12797
  let sum = 0;
12657
12798
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
12658
- const value = sumRange[i][j].value;
12799
+ const value = sumRange[i]?.[j]?.value;
12659
12800
  if (typeof value === "number") {
12660
12801
  sum += value;
12661
12802
  }
@@ -12680,7 +12821,7 @@ const SUMIFS = {
12680
12821
  compute: function (sumRange, ...criters) {
12681
12822
  let sum = 0;
12682
12823
  visitMatchingRanges(criters, (i, j) => {
12683
- const value = sumRange[i][j].value;
12824
+ const value = sumRange[i]?.[j]?.value;
12684
12825
  if (typeof value === "number") {
12685
12826
  sum += value;
12686
12827
  }
@@ -13227,7 +13368,7 @@ const AVERAGEIF = {
13227
13368
  let count = 0;
13228
13369
  let sum = 0;
13229
13370
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
13230
- const value = _averageRange[i][j].value;
13371
+ const value = _averageRange[i]?.[j]?.value;
13231
13372
  if (typeof value === "number") {
13232
13373
  count += 1;
13233
13374
  sum += value;
@@ -13256,7 +13397,7 @@ const AVERAGEIFS = {
13256
13397
  let count = 0;
13257
13398
  let sum = 0;
13258
13399
  visitMatchingRanges(args, (i, j) => {
13259
- const value = _averageRange[i][j].value;
13400
+ const value = _averageRange[i]?.[j]?.value;
13260
13401
  if (typeof value === "number") {
13261
13402
  count += 1;
13262
13403
  sum += value;
@@ -13561,7 +13702,7 @@ const MAXIFS = {
13561
13702
  compute: function (range, ...args) {
13562
13703
  let result = -Infinity;
13563
13704
  visitMatchingRanges(args, (i, j) => {
13564
- const value = range[i][j].value;
13705
+ const value = range[i]?.[j]?.value;
13565
13706
  if (typeof value === "number") {
13566
13707
  result = result < value ? value : result;
13567
13708
  }
@@ -13644,7 +13785,7 @@ const MINIFS = {
13644
13785
  compute: function (range, ...args) {
13645
13786
  let result = Infinity;
13646
13787
  visitMatchingRanges(args, (i, j) => {
13647
- const value = range[i][j].value;
13788
+ const value = range[i]?.[j]?.value;
13648
13789
  if (typeof value === "number") {
13649
13790
  result = result > value ? value : result;
13650
13791
  }
@@ -22101,9 +22242,9 @@ const GAUGE_TEXT_COLOR_HIGH_CONTRAST = "#C8C8C8";
22101
22242
  const GAUGE_INFLECTION_MARKER_COLOR = "#666666aa";
22102
22243
  const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
22103
22244
  const GAUGE_TITLE_SECTION_HEIGHT = 25;
22104
- const GAUGE_TITLE_FONT_SIZE = 18;
22105
- const GAUGE_TITLE_PADDING_LEFT = 10;
22106
- const GAUGE_TITLE_PADDING_TOP = 5;
22245
+ const GAUGE_TITLE_FONT_SIZE = DEFAULT_CHART_FONT_SIZE;
22246
+ const GAUGE_TITLE_PADDING_LEFT = DEFAULT_CHART_PADDING;
22247
+ const GAUGE_TITLE_PADDING_TOP = DEFAULT_CHART_PADDING;
22107
22248
  function drawGaugeChart(canvas, runtime) {
22108
22249
  const canvasBoundingRect = canvas.getBoundingClientRect();
22109
22250
  canvas.width = canvasBoundingRect.width;
@@ -22178,7 +22319,8 @@ function drawInflectionValues(ctx, config) {
22178
22319
  function drawTitle(ctx, config) {
22179
22320
  ctx.save();
22180
22321
  const title = config.title;
22181
- ctx.font = `${title.fontSize}px ${DEFAULT_FONT}`;
22322
+ ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
22323
+ ctx.textBaseline = "middle";
22182
22324
  ctx.fillStyle = title.color;
22183
22325
  ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
22184
22326
  ctx.restore();
@@ -22187,7 +22329,7 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22187
22329
  const maxValue = runtime.maxValue;
22188
22330
  const minValue = runtime.minValue;
22189
22331
  const gaugeValue = runtime.gaugeValue;
22190
- const gaugeRect = getGaugeRect(boundingRect, runtime.title);
22332
+ const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
22191
22333
  const gaugeArcWidth = gaugeRect.width / 6;
22192
22334
  const gaugePercentage = gaugeValue
22193
22335
  ? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
@@ -22217,17 +22359,35 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22217
22359
  };
22218
22360
  const textColor = getContrastedTextColor(runtime.background);
22219
22361
  const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
22362
+ let x = 0, titleWidth = 0, titleHeight = 0;
22363
+ if (runtime.title.text) {
22364
+ ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { ...runtime.title, fontSize: GAUGE_TITLE_FONT_SIZE }, "px"));
22365
+ }
22366
+ switch (runtime.title.align) {
22367
+ case "right":
22368
+ x = boundingRect.width - titleWidth - GAUGE_TITLE_PADDING_LEFT;
22369
+ break;
22370
+ case "center":
22371
+ x = (boundingRect.width - titleWidth) / 2;
22372
+ break;
22373
+ case "left":
22374
+ default:
22375
+ x = GAUGE_TITLE_PADDING_LEFT;
22376
+ break;
22377
+ }
22220
22378
  return {
22221
22379
  width: boundingRect.width,
22222
22380
  height: boundingRect.height,
22223
22381
  title: {
22224
- label: runtime.title,
22382
+ label: runtime.title.text ?? "",
22225
22383
  fontSize: GAUGE_TITLE_FONT_SIZE,
22226
22384
  textPosition: {
22227
- x: GAUGE_TITLE_PADDING_LEFT,
22228
- y: GAUGE_TITLE_PADDING_TOP + GAUGE_TITLE_FONT_SIZE,
22385
+ x,
22386
+ y: GAUGE_TITLE_PADDING_TOP + titleHeight / 2,
22229
22387
  },
22230
- color: textColor,
22388
+ color: runtime.title.color ?? textColor,
22389
+ bold: runtime.title.bold,
22390
+ italic: runtime.title.italic,
22231
22391
  },
22232
22392
  backgroundColor: runtime.background,
22233
22393
  gauge: {
@@ -22517,12 +22677,18 @@ function truncateLabel(label) {
22517
22677
  * Get a default chart js configuration
22518
22678
  */
22519
22679
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22680
+ const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22520
22681
  const options = {
22521
22682
  // https://www.chartjs.org/docs/latest/general/responsive.html
22522
22683
  responsive: true, // will resize when its container is resized
22523
22684
  maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
22524
22685
  layout: {
22525
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
22686
+ padding: {
22687
+ left: DEFAULT_CHART_PADDING,
22688
+ right: DEFAULT_CHART_PADDING,
22689
+ top: chartTitle.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
22690
+ bottom: DEFAULT_CHART_PADDING,
22691
+ },
22526
22692
  },
22527
22693
  elements: {
22528
22694
  line: {
@@ -22535,10 +22701,15 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
22535
22701
  animation: false,
22536
22702
  plugins: {
22537
22703
  title: {
22538
- display: !!chart.title,
22539
- text: _t(chart.title),
22540
- color: fontColor,
22541
- font: { size: 22, weight: "normal" },
22704
+ display: !!chartTitle.text,
22705
+ text: _t(chartTitle.text),
22706
+ color: chartTitle?.color ?? fontColor,
22707
+ align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
22708
+ font: {
22709
+ size: DEFAULT_CHART_FONT_SIZE,
22710
+ weight: chartTitle.bold ? "bold" : "normal",
22711
+ style: chartTitle.italic ? "italic" : "normal",
22712
+ },
22542
22713
  },
22543
22714
  legend: {
22544
22715
  // Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.
@@ -22673,9 +22844,10 @@ function chartToImage(runtime, figure, type) {
22673
22844
  // we have to add the canvas to the DOM otherwise it won't be rendered
22674
22845
  document.body.append(div);
22675
22846
  if ("chartJsConfig" in runtime) {
22676
- runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
22847
+ const config = deepCopy(runtime.chartJsConfig);
22848
+ config.plugins = [backgroundColorChartJSPlugin];
22677
22849
  // @ts-ignore
22678
- const chart = new window.Chart(canvas, runtime.chartJsConfig);
22850
+ const chart = new window.Chart(canvas, config);
22679
22851
  const imgContent = chart.toBase64Image();
22680
22852
  chart.destroy();
22681
22853
  div.remove();
@@ -22716,22 +22888,24 @@ class BarChart extends AbstractChart {
22716
22888
  dataSets;
22717
22889
  labelRange;
22718
22890
  background;
22719
- verticalAxisPosition;
22720
22891
  legendPosition;
22721
22892
  stacked;
22722
22893
  aggregated;
22723
22894
  type = "bar";
22724
22895
  dataSetsHaveTitle;
22896
+ dataSetDesign;
22897
+ axesDesign;
22725
22898
  constructor(definition, sheetId, getters) {
22726
22899
  super(definition, sheetId, getters);
22727
22900
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
22728
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
22901
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
22729
22902
  this.background = definition.background;
22730
- this.verticalAxisPosition = definition.verticalAxisPosition;
22731
22903
  this.legendPosition = definition.legendPosition;
22732
22904
  this.stacked = definition.stacked;
22733
22905
  this.aggregated = definition.aggregated;
22734
22906
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22907
+ this.dataSetDesign = definition.dataSets;
22908
+ this.axesDesign = definition.axesDesign;
22735
22909
  }
22736
22910
  static transformDefinition(definition, executed) {
22737
22911
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22742,21 +22916,28 @@ class BarChart extends AbstractChart {
22742
22916
  static getDefinitionFromContextCreation(context) {
22743
22917
  return {
22744
22918
  background: context.background,
22745
- dataSets: context.range ? context.range : [],
22919
+ dataSets: context.range ?? [],
22746
22920
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22747
22921
  stacked: context.stacked ?? false,
22748
22922
  aggregated: context.aggregated ?? false,
22749
22923
  legendPosition: context.legendPosition ?? "top",
22750
- title: context.title || "",
22924
+ title: context.title || { text: "" },
22751
22925
  type: "bar",
22752
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
22753
22926
  labelRange: context.auxiliaryRange || undefined,
22927
+ axesDesign: context.axesDesign,
22754
22928
  };
22755
22929
  }
22756
22930
  getContextCreation() {
22931
+ const range = [];
22932
+ for (const [i, dataSet] of this.dataSets.entries()) {
22933
+ range.push({
22934
+ ...this.dataSetDesign?.[i],
22935
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
22936
+ });
22937
+ }
22757
22938
  return {
22758
22939
  ...this,
22759
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
22940
+ range,
22760
22941
  auxiliaryRange: this.labelRange
22761
22942
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22762
22943
  : undefined,
@@ -22776,19 +22957,26 @@ class BarChart extends AbstractChart {
22776
22957
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
22777
22958
  }
22778
22959
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
22960
+ const ranges = [];
22961
+ for (const [i, dataSet] of dataSets.entries()) {
22962
+ ranges.push({
22963
+ ...this.dataSetDesign?.[i],
22964
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
22965
+ });
22966
+ }
22779
22967
  return {
22780
22968
  type: "bar",
22781
22969
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
22782
22970
  background: this.background,
22783
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
22971
+ dataSets: ranges,
22784
22972
  legendPosition: this.legendPosition,
22785
- verticalAxisPosition: this.verticalAxisPosition,
22786
22973
  labelRange: labelRange
22787
22974
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
22788
22975
  : undefined,
22789
22976
  title: this.title,
22790
22977
  stacked: this.stacked,
22791
22978
  aggregated: this.aggregated,
22979
+ axesDesign: this.axesDesign,
22792
22980
  };
22793
22981
  }
22794
22982
  getDefinitionForExcel() {
@@ -22799,12 +22987,14 @@ class BarChart extends AbstractChart {
22799
22987
  .map((ds) => toExcelDataset(this.getters, ds))
22800
22988
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22801
22989
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
22990
+ const definition = this.getDefinition();
22802
22991
  return {
22803
- ...this.getDefinition(),
22992
+ ...definition,
22804
22993
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
22805
22994
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
22806
22995
  dataSets,
22807
22996
  labelRange,
22997
+ verticalAxis: getDefinedAxis(definition),
22808
22998
  };
22809
22999
  }
22810
23000
  updateRanges(applyChange) {
@@ -22838,30 +23028,51 @@ function getBarConfiguration(chart, labels, localeFormat) {
22838
23028
  padding: 5,
22839
23029
  color: fontColor,
22840
23030
  },
23031
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
22841
23032
  },
22842
- y: {
22843
- position: chart.verticalAxisPosition,
22844
- beginAtZero: true, // the origin of the y axis is always zero
22845
- ticks: {
22846
- color: fontColor,
22847
- callback: (value) => {
22848
- value = Number(value);
22849
- if (isNaN(value))
22850
- return value;
22851
- const { locale, format } = localeFormat;
22852
- return formatValue(value, {
22853
- locale,
22854
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
22855
- });
22856
- },
23033
+ };
23034
+ const yAxis = {
23035
+ beginAtZero: true, // the origin of the y axis is always zero
23036
+ ticks: {
23037
+ color: fontColor,
23038
+ callback: (value) => {
23039
+ value = Number(value);
23040
+ if (isNaN(value))
23041
+ return value;
23042
+ const { locale, format } = localeFormat;
23043
+ return formatValue(value, {
23044
+ locale,
23045
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23046
+ });
22857
23047
  },
22858
23048
  },
22859
23049
  };
23050
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23051
+ if (useLeftAxis) {
23052
+ config.options.scales.y = {
23053
+ ...yAxis,
23054
+ position: "left",
23055
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23056
+ };
23057
+ }
23058
+ if (useRightAxis) {
23059
+ config.options.scales.y1 = {
23060
+ ...yAxis,
23061
+ position: "right",
23062
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23063
+ };
23064
+ }
22860
23065
  if (chart.stacked) {
22861
23066
  // @ts-ignore chart.js type is broken
22862
23067
  config.options.scales.x.stacked = true;
22863
- // @ts-ignore chart.js type is broken
22864
- config.options.scales.y.stacked = true;
23068
+ if (useLeftAxis) {
23069
+ // @ts-ignore chart.js type is broken
23070
+ config.options.scales.y.stacked = true;
23071
+ }
23072
+ if (useRightAxis) {
23073
+ // @ts-ignore chart.js type is broken
23074
+ config.options.scales.y1.stacked = true;
23075
+ }
22865
23076
  }
22866
23077
  return config;
22867
23078
  }
@@ -22881,8 +23092,9 @@ function createBarChartRuntime(chart, getters) {
22881
23092
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
22882
23093
  const locale = getters.getLocale();
22883
23094
  const config = getBarConfiguration(chart, labels, { format: dataSetFormat, locale });
22884
- const colors = new ChartColors();
22885
- for (let { label, data } of dataSetsValues) {
23095
+ const colors = new ColorGenerator();
23096
+ const definition = chart.getDefinition();
23097
+ for (const { label, data } of dataSetsValues) {
22886
23098
  const color = colors.next();
22887
23099
  const dataset = {
22888
23100
  label,
@@ -22892,29 +23104,43 @@ function createBarChartRuntime(chart, getters) {
22892
23104
  };
22893
23105
  config.data.datasets.push(dataset);
22894
23106
  }
23107
+ for (const [index, dataset] of config.data.datasets.entries()) {
23108
+ if (definition.dataSets?.[index]?.backgroundColor) {
23109
+ const color = definition.dataSets[index].backgroundColor;
23110
+ dataset.backgroundColor = color;
23111
+ dataset.borderColor = color;
23112
+ }
23113
+ if (definition.dataSets?.[index]?.label) {
23114
+ const label = definition.dataSets[index].label;
23115
+ dataset.label = label;
23116
+ }
23117
+ if (definition.dataSets?.[index]?.yAxisId) {
23118
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23119
+ }
23120
+ }
22895
23121
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
22896
23122
  }
22897
23123
 
22898
23124
  class ComboChart extends AbstractChart {
22899
- useBothYAxis;
22900
23125
  dataSets;
22901
23126
  labelRange;
22902
23127
  background;
22903
- verticalAxisPosition;
22904
23128
  legendPosition;
22905
23129
  aggregated;
22906
23130
  dataSetsHaveTitle;
23131
+ dataSetDesign;
23132
+ axesDesign;
22907
23133
  type = "combo";
22908
23134
  constructor(definition, sheetId, getters) {
22909
23135
  super(definition, sheetId, getters);
22910
23136
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
22911
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
23137
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
22912
23138
  this.background = definition.background;
22913
- this.verticalAxisPosition = definition.verticalAxisPosition;
22914
23139
  this.legendPosition = definition.legendPosition;
22915
23140
  this.aggregated = definition.aggregated;
22916
23141
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22917
- this.useBothYAxis = definition.useBothYAxis;
23142
+ this.dataSetDesign = definition.dataSets;
23143
+ this.axesDesign = definition.axesDesign;
22918
23144
  }
22919
23145
  static transformDefinition(definition, executed) {
22920
23146
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22923,9 +23149,16 @@ class ComboChart extends AbstractChart {
22923
23149
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
22924
23150
  }
22925
23151
  getContextCreation() {
23152
+ const range = [];
23153
+ for (const [i, dataSet] of this.dataSets.entries()) {
23154
+ range.push({
23155
+ ...this.dataSetDesign?.[i],
23156
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
23157
+ });
23158
+ }
22926
23159
  return {
22927
23160
  ...this,
22928
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23161
+ range,
22929
23162
  auxiliaryRange: this.labelRange
22930
23163
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22931
23164
  : undefined,
@@ -22935,19 +23168,25 @@ class ComboChart extends AbstractChart {
22935
23168
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
22936
23169
  }
22937
23170
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
23171
+ const ranges = [];
23172
+ for (const [i, dataSet] of dataSets.entries()) {
23173
+ ranges.push({
23174
+ ...this.dataSetDesign?.[i],
23175
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
23176
+ });
23177
+ }
22938
23178
  return {
22939
23179
  type: "combo",
22940
23180
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
22941
23181
  background: this.background,
22942
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
23182
+ dataSets: ranges,
22943
23183
  legendPosition: this.legendPosition,
22944
- verticalAxisPosition: this.verticalAxisPosition,
22945
23184
  labelRange: labelRange
22946
23185
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
22947
23186
  : undefined,
22948
23187
  title: this.title,
22949
23188
  aggregated: this.aggregated,
22950
- useBothYAxis: this.useBothYAxis,
23189
+ axesDesign: this.axesDesign,
22951
23190
  };
22952
23191
  }
22953
23192
  getDefinitionForExcel() {
@@ -22959,12 +23198,14 @@ class ComboChart extends AbstractChart {
22959
23198
  .map((ds) => toExcelDataset(this.getters, ds))
22960
23199
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22961
23200
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
23201
+ const definition = this.getDefinition();
22962
23202
  return {
22963
- ...this.getDefinition(),
23203
+ ...definition,
22964
23204
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
22965
23205
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
22966
23206
  dataSets,
22967
23207
  labelRange,
23208
+ verticalAxis: getDefinedAxis(definition),
22968
23209
  };
22969
23210
  }
22970
23211
  updateRanges(applyChange) {
@@ -22982,11 +23223,10 @@ class ComboChart extends AbstractChart {
22982
23223
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22983
23224
  aggregated: context.aggregated,
22984
23225
  legendPosition: context.legendPosition ?? "top",
22985
- title: context.title || "",
22986
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
23226
+ title: context.title || { text: "" },
22987
23227
  labelRange: context.auxiliaryRange || undefined,
22988
23228
  type: "combo",
22989
- useBothYAxis: false,
23229
+ axesDesign: context.axesDesign,
22990
23230
  };
22991
23231
  }
22992
23232
  copyForSheetId(sheetId) {
@@ -23001,7 +23241,10 @@ class ComboChart extends AbstractChart {
23001
23241
  }
23002
23242
  }
23003
23243
  function createComboChartRuntime(chart, getters) {
23004
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
23244
+ const mainDataSetFormat = chart.dataSets.length
23245
+ ? getChartDatasetFormat(getters, [chart.dataSets[0]])
23246
+ : undefined;
23247
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
23005
23248
  const locale = getters.getLocale();
23006
23249
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23007
23250
  let labels = labelValues.formattedValues;
@@ -23015,11 +23258,12 @@ function createComboChartRuntime(chart, getters) {
23015
23258
  if (chart.aggregated) {
23016
23259
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
23017
23260
  }
23018
- const localeFormat = { format: dataSetFormat, locale };
23261
+ const localeFormat = { format: mainDataSetFormat, locale };
23019
23262
  const fontColor = chartFontColor(chart.background);
23020
23263
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23021
23264
  const legend = {
23022
23265
  labels: { color: fontColor },
23266
+ reverse: true,
23023
23267
  };
23024
23268
  if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
23025
23269
  legend.display = false;
@@ -23037,52 +23281,64 @@ function createComboChartRuntime(chart, getters) {
23037
23281
  padding: 5,
23038
23282
  color: fontColor,
23039
23283
  },
23284
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23040
23285
  },
23041
23286
  };
23042
- const verticalAxis = {
23287
+ const formatCallback = (format) => {
23288
+ return (value) => {
23289
+ value = Number(value);
23290
+ if (isNaN(value))
23291
+ return value;
23292
+ const { locale } = localeFormat;
23293
+ return formatValue(value, {
23294
+ locale,
23295
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23296
+ });
23297
+ };
23298
+ };
23299
+ const leftVerticalAxis = {
23043
23300
  beginAtZero: true, // the origin of the y axis is always zero
23044
23301
  ticks: {
23045
23302
  color: fontColor,
23046
- callback: (value) => {
23047
- value = Number(value);
23048
- if (isNaN(value))
23049
- return value;
23050
- const { locale, format } = localeFormat;
23051
- return formatValue(value, {
23052
- locale,
23053
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23054
- });
23055
- },
23303
+ callback: formatCallback(mainDataSetFormat),
23056
23304
  },
23057
23305
  };
23058
- if (chart.useBothYAxis) {
23306
+ const rightVerticalAxis = {
23307
+ beginAtZero: true, // the origin of the y axis is always zero
23308
+ ticks: {
23309
+ color: fontColor,
23310
+ callback: formatCallback(lineDataSetsFormat),
23311
+ },
23312
+ };
23313
+ const definition = chart.getDefinition();
23314
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23315
+ if (useLeftAxis) {
23059
23316
  config.options.scales.y = {
23060
- ...verticalAxis,
23317
+ ...leftVerticalAxis,
23061
23318
  position: "left",
23319
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23062
23320
  };
23321
+ }
23322
+ if (useRightAxis) {
23063
23323
  config.options.scales.y1 = {
23064
- ...verticalAxis,
23324
+ ...rightVerticalAxis,
23065
23325
  position: "right",
23066
23326
  grid: {
23067
23327
  display: false,
23068
23328
  },
23329
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23069
23330
  };
23070
23331
  }
23071
- else {
23072
- config.options.scales.y = {
23073
- ...verticalAxis,
23074
- position: chart.verticalAxisPosition,
23075
- };
23076
- }
23077
- const colors = new ChartColors();
23332
+ const colors = new ColorGenerator();
23078
23333
  for (let [index, { label, data }] of dataSetsValues.entries()) {
23334
+ const design = definition.dataSets[index];
23079
23335
  const color = colors.next();
23080
23336
  const dataset = {
23081
- label,
23337
+ label: design?.label ?? label,
23082
23338
  data,
23083
- borderColor: color,
23084
- backgroundColor: color,
23085
- yAxisID: index > 0 && chart.useBothYAxis ? "y1" : "y",
23339
+ borderColor: design?.backgroundColor ?? color,
23340
+ backgroundColor: design.backgroundColor ?? color,
23341
+ yAxisID: design?.yAxisId ?? "y",
23086
23342
  type: index === 0 ? "bar" : "line",
23087
23343
  order: -index,
23088
23344
  };
@@ -23163,7 +23419,7 @@ class GaugeChart extends AbstractChart {
23163
23419
  type = "gauge";
23164
23420
  constructor(definition, sheetId, getters) {
23165
23421
  super(definition, sheetId, getters);
23166
- this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);
23422
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
23167
23423
  this.sectionRule = definition.sectionRule;
23168
23424
  this.background = definition.background;
23169
23425
  }
@@ -23183,9 +23439,9 @@ class GaugeChart extends AbstractChart {
23183
23439
  static getDefinitionFromContextCreation(context) {
23184
23440
  return {
23185
23441
  background: context.background,
23186
- title: context.title || "",
23442
+ title: context.title || { text: "" },
23187
23443
  type: "gauge",
23188
- dataRange: context.range ? context.range[0] : undefined,
23444
+ dataRange: context.range ? context.range[0].dataRange : undefined,
23189
23445
  sectionRule: {
23190
23446
  colors: {
23191
23447
  lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
@@ -23236,7 +23492,7 @@ class GaugeChart extends AbstractChart {
23236
23492
  return {
23237
23493
  ...this,
23238
23494
  range: this.dataRange
23239
- ? [this.getters.getRangeString(this.dataRange, this.sheetId)]
23495
+ ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
23240
23496
  : undefined,
23241
23497
  };
23242
23498
  }
@@ -23299,7 +23555,7 @@ function createGaugeChartRuntime(chart, getters) {
23299
23555
  colors.push(chartColors.upperColor);
23300
23556
  return {
23301
23557
  background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
23302
- title: chart.title,
23558
+ title: chart.title ?? { text: "" },
23303
23559
  minValue: {
23304
23560
  value: minValue,
23305
23561
  label: formatValue(minValue, { locale, format }),
@@ -23557,28 +23813,49 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23557
23813
  padding: 5,
23558
23814
  color: fontColor,
23559
23815
  },
23816
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23560
23817
  },
23561
- y: {
23562
- position: chart.verticalAxisPosition,
23563
- beginAtZero: true, // the origin of the y axis is always zero
23564
- ticks: {
23565
- color: fontColor,
23566
- callback: (value) => {
23567
- value = Number(value);
23568
- if (isNaN(value))
23569
- return value;
23570
- const { locale, format } = options;
23571
- return formatValue(value, {
23572
- locale,
23573
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23574
- });
23575
- },
23818
+ };
23819
+ const yAxis = {
23820
+ beginAtZero: true, // the origin of the y axis is always zero
23821
+ ticks: {
23822
+ color: fontColor,
23823
+ callback: (value) => {
23824
+ value = Number(value);
23825
+ if (isNaN(value))
23826
+ return value;
23827
+ const { locale, format } = options;
23828
+ return formatValue(value, {
23829
+ locale,
23830
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23831
+ });
23576
23832
  },
23577
23833
  },
23578
23834
  };
23579
- if ("stacked" in chart && chart.stacked && config.options?.scales?.y) {
23580
- // @ts-ignore chart.js type is wrong
23581
- config.options.scales.y.stacked = true;
23835
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23836
+ if (useLeftAxis) {
23837
+ config.options.scales.y = {
23838
+ ...yAxis,
23839
+ position: "left",
23840
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23841
+ };
23842
+ }
23843
+ if (useRightAxis) {
23844
+ config.options.scales.y1 = {
23845
+ ...yAxis,
23846
+ position: "right",
23847
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23848
+ };
23849
+ }
23850
+ if ("stacked" in chart && chart.stacked) {
23851
+ if (useLeftAxis) {
23852
+ // @ts-ignore chart.js type is broken
23853
+ config.options.scales.y.stacked = true;
23854
+ }
23855
+ if (useRightAxis) {
23856
+ // @ts-ignore chart.js type is broken
23857
+ config.options.scales.y1.stacked = true;
23858
+ }
23582
23859
  }
23583
23860
  return config;
23584
23861
  }
@@ -23625,7 +23902,8 @@ function createLineOrScatterChartRuntime(chart, getters) {
23625
23902
  }
23626
23903
  const stacked = "stacked" in chart ? chart.stacked : false;
23627
23904
  const cumulative = "cumulative" in chart ? chart.cumulative : false;
23628
- const colors = new ChartColors();
23905
+ const colors = new ColorGenerator();
23906
+ const definition = chart.getDefinition();
23629
23907
  for (let [index, { label, data }] of dataSetsValues.entries()) {
23630
23908
  if (["linear", "time"].includes(axisType)) {
23631
23909
  // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
@@ -23658,6 +23936,22 @@ function createLineOrScatterChartRuntime(chart, getters) {
23658
23936
  };
23659
23937
  config.data.datasets.push(dataset);
23660
23938
  }
23939
+ for (const [index, dataset] of config.data.datasets.entries()) {
23940
+ if (definition.dataSets?.[index]?.backgroundColor) {
23941
+ const color = definition.dataSets[index].backgroundColor;
23942
+ dataset.backgroundColor = color;
23943
+ dataset.borderColor = color;
23944
+ //@ts-ignore
23945
+ dataset.pointBackgroundColor = color;
23946
+ }
23947
+ if (definition.dataSets?.[index]?.label) {
23948
+ const label = definition.dataSets[index].label;
23949
+ dataset.label = label;
23950
+ }
23951
+ if (definition.dataSets?.[index]?.yAxisId) {
23952
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23953
+ }
23954
+ }
23661
23955
  return {
23662
23956
  chartJsConfig: config,
23663
23957
  background: chart.background || BACKGROUND_CHART_COLOR,
@@ -23672,7 +23966,6 @@ class LineChart extends AbstractChart {
23672
23966
  dataSets;
23673
23967
  labelRange;
23674
23968
  background;
23675
- verticalAxisPosition;
23676
23969
  legendPosition;
23677
23970
  labelsAsText;
23678
23971
  stacked;
@@ -23680,18 +23973,21 @@ class LineChart extends AbstractChart {
23680
23973
  type = "line";
23681
23974
  dataSetsHaveTitle;
23682
23975
  cumulative;
23976
+ dataSetDesign;
23977
+ axesDesign;
23683
23978
  constructor(definition, sheetId, getters) {
23684
23979
  super(definition, sheetId, getters);
23685
23980
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23686
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
23981
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
23687
23982
  this.background = definition.background;
23688
- this.verticalAxisPosition = definition.verticalAxisPosition;
23689
23983
  this.legendPosition = definition.legendPosition;
23690
23984
  this.labelsAsText = definition.labelsAsText;
23691
23985
  this.stacked = definition.stacked;
23692
23986
  this.aggregated = definition.aggregated;
23693
23987
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
23694
23988
  this.cumulative = definition.cumulative;
23989
+ this.dataSetDesign = definition.dataSets;
23990
+ this.axesDesign = definition.axesDesign;
23695
23991
  }
23696
23992
  static validateChartDefinition(validator, definition) {
23697
23993
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -23702,30 +23998,36 @@ class LineChart extends AbstractChart {
23702
23998
  static getDefinitionFromContextCreation(context) {
23703
23999
  return {
23704
24000
  background: context.background,
23705
- dataSets: context.range ? context.range : [],
24001
+ dataSets: context.range ?? [],
23706
24002
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23707
24003
  labelsAsText: context.labelsAsText ?? false,
23708
24004
  legendPosition: context.legendPosition ?? "top",
23709
- title: context.title || "",
24005
+ title: context.title || { text: "" },
23710
24006
  type: "line",
23711
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
23712
24007
  labelRange: context.auxiliaryRange || undefined,
23713
24008
  stacked: context.stacked ?? false,
23714
24009
  aggregated: context.aggregated ?? false,
23715
24010
  cumulative: context.cumulative ?? false,
24011
+ axesDesign: context.axesDesign,
23716
24012
  };
23717
24013
  }
23718
24014
  getDefinition() {
23719
24015
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
23720
24016
  }
23721
24017
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24018
+ const ranges = [];
24019
+ for (const [i, dataSet] of dataSets.entries()) {
24020
+ ranges.push({
24021
+ ...this.dataSetDesign?.[i],
24022
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24023
+ });
24024
+ }
23722
24025
  return {
23723
24026
  type: "line",
23724
24027
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23725
24028
  background: this.background,
23726
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24029
+ dataSets: ranges,
23727
24030
  legendPosition: this.legendPosition,
23728
- verticalAxisPosition: this.verticalAxisPosition,
23729
24031
  labelRange: labelRange
23730
24032
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
23731
24033
  : undefined,
@@ -23734,12 +24036,20 @@ class LineChart extends AbstractChart {
23734
24036
  stacked: this.stacked,
23735
24037
  aggregated: this.aggregated,
23736
24038
  cumulative: this.cumulative,
24039
+ axesDesign: this.axesDesign,
23737
24040
  };
23738
24041
  }
23739
24042
  getContextCreation() {
24043
+ const range = [];
24044
+ for (const [i, dataSet] of this.dataSets.entries()) {
24045
+ range.push({
24046
+ ...this.dataSetDesign?.[i],
24047
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24048
+ });
24049
+ }
23740
24050
  return {
23741
24051
  ...this,
23742
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24052
+ range,
23743
24053
  auxiliaryRange: this.labelRange
23744
24054
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23745
24055
  : undefined,
@@ -23761,12 +24071,14 @@ class LineChart extends AbstractChart {
23761
24071
  .map((ds) => toExcelDataset(this.getters, ds))
23762
24072
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
23763
24073
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24074
+ const definition = this.getDefinition();
23764
24075
  return {
23765
- ...this.getDefinition(),
24076
+ ...definition,
23766
24077
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23767
24078
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
23768
24079
  dataSets,
23769
24080
  labelRange,
24081
+ verticalAxis: getDefinedAxis(definition),
23770
24082
  };
23771
24083
  }
23772
24084
  copyForSheetId(sheetId) {
@@ -23796,7 +24108,7 @@ class PieChart extends AbstractChart {
23796
24108
  constructor(definition, sheetId, getters) {
23797
24109
  super(definition, sheetId, getters);
23798
24110
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23799
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
24111
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
23800
24112
  this.background = definition.background;
23801
24113
  this.legendPosition = definition.legendPosition;
23802
24114
  this.aggregated = definition.aggregated;
@@ -23811,10 +24123,10 @@ class PieChart extends AbstractChart {
23811
24123
  static getDefinitionFromContextCreation(context) {
23812
24124
  return {
23813
24125
  background: context.background,
23814
- dataSets: context.range ? context.range : [],
24126
+ dataSets: context.range ?? [],
23815
24127
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23816
24128
  legendPosition: context.legendPosition ?? "top",
23817
- title: context.title || "",
24129
+ title: context.title || { text: "" },
23818
24130
  type: "pie",
23819
24131
  labelRange: context.auxiliaryRange || undefined,
23820
24132
  aggregated: context.aggregated ?? false,
@@ -23826,7 +24138,9 @@ class PieChart extends AbstractChart {
23826
24138
  getContextCreation() {
23827
24139
  return {
23828
24140
  ...this,
23829
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24141
+ range: this.dataSets.map((ds) => ({
24142
+ dataRange: this.getters.getRangeString(ds.dataRange, this.sheetId),
24143
+ })),
23830
24144
  auxiliaryRange: this.labelRange
23831
24145
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23832
24146
  : undefined,
@@ -23837,7 +24151,9 @@ class PieChart extends AbstractChart {
23837
24151
  type: "pie",
23838
24152
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23839
24153
  background: this.background,
23840
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24154
+ dataSets: dataSets.map((ds) => ({
24155
+ dataRange: this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId),
24156
+ })),
23841
24157
  legendPosition: this.legendPosition,
23842
24158
  labelRange: labelRange
23843
24159
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
@@ -23868,7 +24184,6 @@ class PieChart extends AbstractChart {
23868
24184
  ...this.getDefinition(),
23869
24185
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23870
24186
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
23871
- verticalAxisPosition: "left", //TODO ExcelChartDefinition should be adapted, but can be done later
23872
24187
  dataSets,
23873
24188
  labelRange,
23874
24189
  };
@@ -23969,9 +24284,8 @@ function createPieChartRuntime(chart, getters) {
23969
24284
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
23970
24285
  const locale = getters.getLocale();
23971
24286
  const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
23972
- const colors = new ChartColors();
23973
- for (let { label, data } of dataSetsValues) {
23974
- const backgroundColor = getPieColors(colors, dataSetsValues);
24287
+ const backgroundColor = getPieColors(new ColorGenerator(), dataSetsValues);
24288
+ for (const { label, data } of dataSetsValues) {
23975
24289
  const dataset = {
23976
24290
  label,
23977
24291
  data,
@@ -23987,22 +24301,24 @@ class ScatterChart extends AbstractChart {
23987
24301
  dataSets;
23988
24302
  labelRange;
23989
24303
  background;
23990
- verticalAxisPosition;
23991
24304
  legendPosition;
23992
24305
  labelsAsText;
23993
24306
  aggregated;
23994
24307
  type = "scatter";
23995
24308
  dataSetsHaveTitle;
24309
+ dataSetDesign;
24310
+ axesDesign;
23996
24311
  constructor(definition, sheetId, getters) {
23997
24312
  super(definition, sheetId, getters);
23998
24313
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23999
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
24314
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
24000
24315
  this.background = definition.background;
24001
- this.verticalAxisPosition = definition.verticalAxisPosition;
24002
24316
  this.legendPosition = definition.legendPosition;
24003
24317
  this.labelsAsText = definition.labelsAsText;
24004
24318
  this.aggregated = definition.aggregated;
24005
24319
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24320
+ this.dataSetDesign = definition.dataSets;
24321
+ this.axesDesign = definition.axesDesign;
24006
24322
  }
24007
24323
  static validateChartDefinition(validator, definition) {
24008
24324
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24013,40 +24329,54 @@ class ScatterChart extends AbstractChart {
24013
24329
  static getDefinitionFromContextCreation(context) {
24014
24330
  return {
24015
24331
  background: context.background,
24016
- dataSets: context.range ? context.range : [],
24332
+ dataSets: context.range ?? [],
24017
24333
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24018
24334
  labelsAsText: context.labelsAsText ?? false,
24019
24335
  legendPosition: context.legendPosition ?? "top",
24020
- title: context.title || "",
24336
+ title: context.title || { text: "" },
24021
24337
  type: "scatter",
24022
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
24023
24338
  labelRange: context.auxiliaryRange || undefined,
24024
24339
  aggregated: context.aggregated ?? false,
24340
+ axesDesign: context.axesDesign,
24025
24341
  };
24026
24342
  }
24027
24343
  getDefinition() {
24028
24344
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24029
24345
  }
24030
24346
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24347
+ const ranges = [];
24348
+ for (const [i, dataSet] of dataSets.entries()) {
24349
+ ranges.push({
24350
+ ...this.dataSetDesign?.[i],
24351
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24352
+ });
24353
+ }
24031
24354
  return {
24032
24355
  type: "scatter",
24033
24356
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24034
24357
  background: this.background,
24035
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24358
+ dataSets: ranges,
24036
24359
  legendPosition: this.legendPosition,
24037
- verticalAxisPosition: this.verticalAxisPosition,
24038
24360
  labelRange: labelRange
24039
24361
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24040
24362
  : undefined,
24041
24363
  title: this.title,
24042
24364
  labelsAsText: this.labelsAsText,
24043
24365
  aggregated: this.aggregated,
24366
+ axesDesign: this.axesDesign,
24044
24367
  };
24045
24368
  }
24046
24369
  getContextCreation() {
24370
+ const range = [];
24371
+ for (const [i, dataSet] of this.dataSets.entries()) {
24372
+ range.push({
24373
+ ...this.dataSetDesign?.[i],
24374
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24375
+ });
24376
+ }
24047
24377
  return {
24048
24378
  ...this,
24049
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24379
+ range,
24050
24380
  auxiliaryRange: this.labelRange
24051
24381
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
24052
24382
  : undefined,
@@ -24069,12 +24399,14 @@ class ScatterChart extends AbstractChart {
24069
24399
  .map((ds) => toExcelDataset(this.getters, ds))
24070
24400
  .filter((ds) => ds.range !== "");
24071
24401
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24402
+ const definition = this.getDefinition();
24072
24403
  return {
24073
- ...this.getDefinition(),
24404
+ ...definition,
24074
24405
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
24075
24406
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
24076
24407
  dataSets,
24077
24408
  labelRange,
24409
+ verticalAxis: getDefinedAxis(definition),
24078
24410
  };
24079
24411
  }
24080
24412
  copyForSheetId(sheetId) {
@@ -24094,13 +24426,6 @@ function createScatterChartRuntime(chart, getters) {
24094
24426
  // have less options than the line chart (it only works with linear labels)
24095
24427
  chartJsConfig.type = "line";
24096
24428
  const configOptions = chartJsConfig.options;
24097
- configOptions.elements = {
24098
- point: {
24099
- radius: 3,
24100
- hoverRadius: 3, // chartJS seems bugged, the point starts with the "hoverRadius" value
24101
- hitRadius: 8,
24102
- },
24103
- };
24104
24429
  const locale = getters.getLocale();
24105
24430
  configOptions.plugins.tooltip.callbacks.title = () => "";
24106
24431
  configOptions.plugins.tooltip.callbacks.label = (tooltipItem) => {
@@ -24137,10 +24462,12 @@ class WaterfallChart extends AbstractChart {
24137
24462
  positiveValuesColor;
24138
24463
  negativeValuesColor;
24139
24464
  subTotalValuesColor;
24465
+ dataSetDesign;
24466
+ axesDesign;
24140
24467
  constructor(definition, sheetId, getters) {
24141
24468
  super(definition, sheetId, getters);
24142
24469
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
24143
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
24470
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24144
24471
  this.background = definition.background;
24145
24472
  this.verticalAxisPosition = definition.verticalAxisPosition;
24146
24473
  this.legendPosition = definition.legendPosition;
@@ -24152,6 +24479,8 @@ class WaterfallChart extends AbstractChart {
24152
24479
  this.negativeValuesColor = definition.negativeValuesColor;
24153
24480
  this.subTotalValuesColor = definition.subTotalValuesColor;
24154
24481
  this.firstValueAsSubtotal = definition.firstValueAsSubtotal;
24482
+ this.dataSetDesign = definition.dataSets;
24483
+ this.axesDesign = definition.axesDesign;
24155
24484
  }
24156
24485
  static transformDefinition(definition, executed) {
24157
24486
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24166,19 +24495,27 @@ class WaterfallChart extends AbstractChart {
24166
24495
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24167
24496
  aggregated: context.aggregated ?? false,
24168
24497
  legendPosition: context.legendPosition ?? "top",
24169
- title: context.title || "",
24498
+ title: context.title || { text: "" },
24170
24499
  type: "waterfall",
24171
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
24500
+ verticalAxisPosition: "left",
24172
24501
  labelRange: context.auxiliaryRange || undefined,
24173
24502
  showSubTotals: context.showSubTotals ?? false,
24174
24503
  showConnectorLines: context.showConnectorLines ?? true,
24175
24504
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
24505
+ axesDesign: context.axesDesign,
24176
24506
  };
24177
24507
  }
24178
24508
  getContextCreation() {
24509
+ const range = [];
24510
+ for (const [i, dataSet] of this.dataSets.entries()) {
24511
+ range.push({
24512
+ ...this.dataSetDesign?.[i],
24513
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24514
+ });
24515
+ }
24179
24516
  return {
24180
24517
  ...this,
24181
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24518
+ range,
24182
24519
  auxiliaryRange: this.labelRange
24183
24520
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
24184
24521
  : undefined,
@@ -24198,11 +24535,18 @@ class WaterfallChart extends AbstractChart {
24198
24535
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24199
24536
  }
24200
24537
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24538
+ const ranges = [];
24539
+ for (const [i, dataSet] of dataSets.entries()) {
24540
+ ranges.push({
24541
+ ...this.dataSetDesign?.[i],
24542
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24543
+ });
24544
+ }
24201
24545
  return {
24202
24546
  type: "waterfall",
24203
24547
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24204
24548
  background: this.background,
24205
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24549
+ dataSets: ranges,
24206
24550
  legendPosition: this.legendPosition,
24207
24551
  verticalAxisPosition: this.verticalAxisPosition,
24208
24552
  labelRange: labelRange
@@ -24216,6 +24560,7 @@ class WaterfallChart extends AbstractChart {
24216
24560
  negativeValuesColor: this.negativeValuesColor,
24217
24561
  subTotalValuesColor: this.subTotalValuesColor,
24218
24562
  firstValueAsSubtotal: this.firstValueAsSubtotal,
24563
+ axesDesign: this.axesDesign,
24219
24564
  };
24220
24565
  }
24221
24566
  getDefinitionForExcel() {
@@ -24275,6 +24620,7 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24275
24620
  grid: {
24276
24621
  display: false,
24277
24622
  },
24623
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
24278
24624
  },
24279
24625
  y: {
24280
24626
  position: chart.verticalAxisPosition,
@@ -24295,6 +24641,7 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24295
24641
  return context.tick.value === 0 ? 2 : 1;
24296
24642
  },
24297
24643
  },
24644
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
24298
24645
  },
24299
24646
  };
24300
24647
  config.options.plugins.tooltip = {
@@ -26354,13 +26701,14 @@ function getSmartChartDefinition(zone, getters) {
26354
26701
  if (!singleColumn) {
26355
26702
  dataSetZone = { ...zone, left: zone.left + 1 };
26356
26703
  }
26357
- const dataSets = [zoneToXc(dataSetZone)];
26704
+ const dataRange = zoneToXc(dataSetZone);
26705
+ const dataSets = [{ dataRange, yAxisId: "y" }];
26358
26706
  const sheetId = getters.getActiveSheetId();
26359
26707
  const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
26360
26708
  if (getZoneArea(zone) === 1 && topLeftCell?.content) {
26361
26709
  return {
26362
26710
  type: "scorecard",
26363
- title: "",
26711
+ title: { text: "" },
26364
26712
  background: topLeftCell.style?.fillColor || undefined,
26365
26713
  keyValue: zoneToXc(zone),
26366
26714
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
@@ -26396,7 +26744,7 @@ function getSmartChartDefinition(zone, getters) {
26396
26744
  const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
26397
26745
  if (canChartParseLabels(labelRange, getters)) {
26398
26746
  return {
26399
- title,
26747
+ title: { text: title },
26400
26748
  dataSets,
26401
26749
  labelsAsText: false,
26402
26750
  stacked: false,
@@ -26405,7 +26753,6 @@ function getSmartChartDefinition(zone, getters) {
26405
26753
  labelRange: labelRangeXc,
26406
26754
  type: "line",
26407
26755
  dataSetsHaveTitle,
26408
- verticalAxisPosition: "left",
26409
26756
  legendPosition: newLegendPos,
26410
26757
  };
26411
26758
  }
@@ -26413,24 +26760,23 @@ function getSmartChartDefinition(zone, getters) {
26413
26760
  if (singleColumn &&
26414
26761
  getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
26415
26762
  return {
26416
- title: "",
26417
- dataSets,
26763
+ title: { text: "" },
26764
+ dataSets: [{ dataRange }],
26418
26765
  aggregated: true,
26419
- labelRange: dataSets[0],
26766
+ labelRange: dataRange,
26420
26767
  type: "pie",
26421
26768
  legendPosition: "top",
26422
26769
  dataSetsHaveTitle: false,
26423
26770
  };
26424
26771
  }
26425
26772
  return {
26426
- title,
26773
+ title: { text: title },
26427
26774
  dataSets,
26428
26775
  labelRange: labelRangeXc,
26429
26776
  type: "bar",
26430
26777
  stacked: false,
26431
26778
  aggregated: false,
26432
26779
  dataSetsHaveTitle,
26433
- verticalAxisPosition: "left",
26434
26780
  legendPosition: newLegendPos,
26435
26781
  };
26436
26782
  }
@@ -29471,6 +29817,41 @@ class OTRegistry extends Registry {
29471
29817
  }
29472
29818
  const otRegistry = new OTRegistry();
29473
29819
 
29820
+ css /* scss */ `
29821
+ .o-checkbox {
29822
+ display: flex;
29823
+ justify-items: center;
29824
+ input {
29825
+ margin-right: 5px;
29826
+ }
29827
+ }
29828
+ `;
29829
+ class Checkbox extends Component {
29830
+ static template = "o-spreadsheet.Checkbox";
29831
+ static props = {
29832
+ label: { type: String, optional: true },
29833
+ value: { type: Boolean, optional: true },
29834
+ className: { type: String, optional: true },
29835
+ name: { type: String, optional: true },
29836
+ title: { type: String, optional: true },
29837
+ disabled: { type: Boolean, optional: true },
29838
+ onChange: Function,
29839
+ };
29840
+ static defaultProps = { value: false };
29841
+ onChange(ev) {
29842
+ const value = ev.target.checked;
29843
+ this.props.onChange(value);
29844
+ }
29845
+ }
29846
+
29847
+ class Section extends Component {
29848
+ static template = "o_spreadsheet.Section";
29849
+ static props = {
29850
+ class: { type: String, optional: true },
29851
+ slots: Object,
29852
+ };
29853
+ }
29854
+
29474
29855
  // The name is misleading and can be confused with the DOM focus.
29475
29856
  class FocusStore {
29476
29857
  mutators = ["focus", "unfocus"];
@@ -29495,6 +29876,7 @@ class FocusStore {
29495
29876
  class SelectionInputStore extends SpreadsheetStore {
29496
29877
  initialRanges;
29497
29878
  inputHasSingleRange;
29879
+ colors;
29498
29880
  mutators = [
29499
29881
  "resetWithRanges",
29500
29882
  "focusById",
@@ -29510,10 +29892,11 @@ class SelectionInputStore extends SpreadsheetStore {
29510
29892
  inputSheetId;
29511
29893
  focusStore = this.get(FocusStore);
29512
29894
  highlightStore = this.get(HighlightStore);
29513
- constructor(get, initialRanges = [], inputHasSingleRange = false) {
29895
+ constructor(get, initialRanges = [], inputHasSingleRange = false, colors = []) {
29514
29896
  super(get);
29515
29897
  this.initialRanges = initialRanges;
29516
29898
  this.inputHasSingleRange = inputHasSingleRange;
29899
+ this.colors = colors;
29517
29900
  if (inputHasSingleRange && initialRanges.length > 1) {
29518
29901
  throw new Error("Input with a single range cannot be instantiated with several range references.");
29519
29902
  }
@@ -29655,11 +30038,12 @@ class SelectionInputStore extends SpreadsheetStore {
29655
30038
  * e.g. ["A1", "Sheet2!B3", "E12"]
29656
30039
  */
29657
30040
  get selectionInputs() {
30041
+ const generator = new ColorGenerator(this.colors);
29658
30042
  return this.ranges.map((input, index) => Object.assign({}, input, {
29659
30043
  color: this.hasMainFocus &&
29660
30044
  this.focusedRangeIndex !== null &&
29661
30045
  this.getters.isRangeValid(input.xc)
29662
- ? input.color
30046
+ ? generator.next()
29663
30047
  : null,
29664
30048
  isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
29665
30049
  isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
@@ -29736,10 +30120,14 @@ class SelectionInputStore extends SpreadsheetStore {
29736
30120
  */
29737
30121
  insertNewRange(index, values) {
29738
30122
  const currentMaxId = Math.max(0, ...this.ranges.map((range) => Number(range.id)));
30123
+ const colors = new ColorGenerator(this.colors);
30124
+ for (let i = 0; i < index; i++) {
30125
+ colors.next();
30126
+ }
29739
30127
  this.ranges.splice(index, 0, ...values.map((xc, i) => ({
29740
30128
  xc,
29741
30129
  id: currentMaxId + i + 1,
29742
- color: colors$1[(currentMaxId + i) % colors$1.length],
30130
+ color: colors.next(),
29743
30131
  })));
29744
30132
  }
29745
30133
  /**
@@ -29883,6 +30271,7 @@ class SelectionInput extends Component {
29883
30271
  class: { type: String, optional: true },
29884
30272
  onSelectionChanged: { type: Function, optional: true },
29885
30273
  onSelectionConfirmed: { type: Function, optional: true },
30274
+ colors: { type: Array, optional: true, default: [] },
29886
30275
  };
29887
30276
  state = useState({
29888
30277
  isMissing: false,
@@ -29907,7 +30296,7 @@ class SelectionInput extends Component {
29907
30296
  }
29908
30297
  setup() {
29909
30298
  useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
29910
- this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false);
30299
+ this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false, this.props.colors);
29911
30300
  onWillUpdateProps((nextProps) => {
29912
30301
  if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
29913
30302
  this.triggerChange();
@@ -29982,6 +30371,26 @@ class SelectionInput extends Component {
29982
30371
  }
29983
30372
  }
29984
30373
 
30374
+ class ChartDataSeries extends Component {
30375
+ static template = "o-spreadsheet.ChartDataSeries";
30376
+ static components = { SelectionInput, Section };
30377
+ static props = {
30378
+ ranges: Array,
30379
+ hasSingleRange: { type: Boolean, optional: true },
30380
+ onSelectionChanged: Function,
30381
+ onSelectionConfirmed: Function,
30382
+ };
30383
+ get ranges() {
30384
+ return this.props.ranges.map((r) => r.dataRange);
30385
+ }
30386
+ get colors() {
30387
+ return this.props.ranges.map((r) => r.backgroundColor);
30388
+ }
30389
+ get title() {
30390
+ return this.props.hasSingleRange ? _t("Data range") : _t("Data series");
30391
+ }
30392
+ }
30393
+
29985
30394
  css /* scss */ `
29986
30395
  .o-validation-error,
29987
30396
  .o-validation-warning {
@@ -30008,55 +30417,6 @@ class ValidationMessages extends Component {
30008
30417
  }
30009
30418
  }
30010
30419
 
30011
- css /* scss */ `
30012
- .o-checkbox {
30013
- display: flex;
30014
- justify-items: center;
30015
- input {
30016
- margin-right: 5px;
30017
- }
30018
- }
30019
- `;
30020
- class Checkbox extends Component {
30021
- static template = "o-spreadsheet.Checkbox";
30022
- static props = {
30023
- label: { type: String, optional: true },
30024
- value: { type: Boolean, optional: true },
30025
- className: { type: String, optional: true },
30026
- name: { type: String, optional: true },
30027
- title: { type: String, optional: true },
30028
- disabled: { type: Boolean, optional: true },
30029
- onChange: Function,
30030
- };
30031
- static defaultProps = { value: false };
30032
- onChange(ev) {
30033
- const value = ev.target.checked;
30034
- this.props.onChange(value);
30035
- }
30036
- }
30037
-
30038
- class Section extends Component {
30039
- static template = "o_spreadsheet.Section";
30040
- static props = {
30041
- class: { type: String, optional: true },
30042
- slots: Object,
30043
- };
30044
- }
30045
-
30046
- class ChartDataSeries extends Component {
30047
- static template = "o-spreadsheet.ChartDataSeries";
30048
- static components = { SelectionInput, Section };
30049
- static props = {
30050
- ranges: Array,
30051
- hasSingleRange: { type: Boolean, optional: true },
30052
- onSelectionChanged: Function,
30053
- onSelectionConfirmed: Function,
30054
- };
30055
- get title() {
30056
- return this.props.hasSingleRange ? _t("Data range") : _t("Data series");
30057
- }
30058
- }
30059
-
30060
30420
  class ChartErrorSection extends Component {
30061
30421
  static template = "o-spreadsheet.ChartErrorSection";
30062
30422
  static components = { Section, ValidationMessages };
@@ -30085,8 +30445,6 @@ class ChartLabelRange extends Component {
30085
30445
  class GenericChartConfigPanel extends Component {
30086
30446
  static template = "o-spreadsheet-GenericChartConfigPanel";
30087
30447
  static components = {
30088
- SelectionInput,
30089
- ValidationMessages,
30090
30448
  ChartDataSeries,
30091
30449
  ChartLabelRange,
30092
30450
  Section,
@@ -30145,7 +30503,10 @@ class GenericChartConfigPanel extends Component {
30145
30503
  * button "confirm" is clicked
30146
30504
  */
30147
30505
  onDataSeriesRangesChanged(ranges) {
30148
- this.dataSeriesRanges = ranges;
30506
+ this.dataSeriesRanges = ranges.map((dataRange, i) => ({
30507
+ ...this.dataSeriesRanges?.[i],
30508
+ dataRange,
30509
+ }));
30149
30510
  this.state.datasetDispatchResult = this.props.canUpdateChart(this.props.figureId, {
30150
30511
  dataSets: this.dataSeriesRanges,
30151
30512
  });
@@ -30188,7 +30549,7 @@ class GenericChartConfigPanel extends Component {
30188
30549
  }
30189
30550
  const getters = this.env.model.getters;
30190
30551
  const sheetId = getters.getActiveSheetId();
30191
- const labelRange = createRange(getters, sheetId, this.labelRange);
30552
+ const labelRange = createValidRange(getters, sheetId, this.labelRange);
30192
30553
  const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
30193
30554
  if (dataSets.length) {
30194
30555
  return dataSets[0].dataRange.zone.top + 1;
@@ -30217,15 +30578,102 @@ class BarConfigPanel extends GenericChartConfigPanel {
30217
30578
  }
30218
30579
  }
30219
30580
 
30581
+ const ANGLE_DOWN = /*xml*/ `
30582
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 224 256">
30583
+ <path d="M201.4 342.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 274.7 86.6 137.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" transform="translate(0, 9) scale(0.5,0.5)"/>
30584
+ </svg>
30585
+ `;
30586
+ const BACKGROUND_COLOR = "#fdfdfd";
30587
+ const BORDER_COLOR = "#8b8b8b";
30588
+ css /* scss */ `
30589
+ .o_side_panel_collapsible_title {
30590
+ font-size: 16px;
30591
+ font-weight: bold;
30592
+ cursor: pointer;
30593
+ padding: 6px 0px 6px 6px !important;
30594
+
30595
+ .collapsor:before {
30596
+ transform: rotate(-90deg);
30597
+ content: url("data:image/svg+xml,${encodeURIComponent(ANGLE_DOWN)}");
30598
+ width: 12px;
30599
+ display: inline-block;
30600
+ margin: 0 5px 0px 2px;
30601
+ height: 22px;
30602
+ transform-origin: 7px 10px;
30603
+ transition: transform 0.2s ease-in-out;
30604
+ }
30605
+ .collapsor:not(.collapsed):before {
30606
+ transform: rotate(0);
30607
+ }
30608
+
30609
+ .collapsor:not(.collapsed) {
30610
+ background-color: ${BACKGROUND_COLOR};
30611
+ border: solid ${BORDER_COLOR} 1px;
30612
+ margin: -3px 1px -6px -5px;
30613
+ border-radius: 5px 5px 0px 0px;
30614
+ border-bottom: 0px;
30615
+ transition-delay: 0s;
30616
+ }
30617
+
30618
+ .collapsor {
30619
+ width: 100%;
30620
+ margin: -2px 2px -5px -4px;
30621
+ padding: 2px 0 6px 4px;
30622
+ background-color: transparent;
30623
+ border: solid ${BORDER_COLOR} 0px;
30624
+ transition-delay: 0.35s;
30625
+ transition-property: all;
30626
+ }
30627
+
30628
+ .collapsor.collapsed {
30629
+ }
30630
+ }
30631
+
30632
+ .collapsible_section {
30633
+ background-color: #fff;
30634
+ border: solid ${BORDER_COLOR} 1px;
30635
+ border-top: 0;
30636
+ border-radius: 0 0 5px 5px;
30637
+ margin: 0px 1px 0px 1px;
30638
+
30639
+ &.collapsing,
30640
+ &.show {
30641
+ background-color: ${BACKGROUND_COLOR};
30642
+ }
30643
+
30644
+ &.collapsing {
30645
+ transition: height 0.35s, background-color 0.35s !important;
30646
+ }
30647
+ }
30648
+ `;
30649
+ let CURRENT_COLLAPSIBLE_ID = 0;
30650
+ class SidePanelCollapsible extends Component {
30651
+ static template = "o-spreadsheet-SidePanelCollapsible";
30652
+ static props = {
30653
+ slots: Object,
30654
+ collapsedAtInit: { type: Boolean, optional: true },
30655
+ class: { type: String, optional: true },
30656
+ };
30657
+ currentId = (CURRENT_COLLAPSIBLE_ID++).toString();
30658
+ }
30659
+
30660
+ /**
30661
+ * Start listening to pointer events and apply the given callbacks.
30662
+ *
30663
+ * @returns A function to remove the listeners.
30664
+ */
30220
30665
  function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
30221
- const _onMouseUp = (ev) => {
30222
- onMouseUp(ev);
30666
+ const removeListeners = () => {
30223
30667
  window.removeEventListener("pointerdown", onMouseDown);
30224
30668
  window.removeEventListener("pointerup", _onMouseUp);
30225
30669
  window.removeEventListener("dragstart", _onDragStart);
30226
30670
  window.removeEventListener("pointermove", onMouseMove);
30227
30671
  window.removeEventListener("wheel", onMouseMove);
30228
30672
  };
30673
+ const _onMouseUp = (ev) => {
30674
+ onMouseUp(ev);
30675
+ removeListeners();
30676
+ };
30229
30677
  function _onDragStart(ev) {
30230
30678
  ev.preventDefault();
30231
30679
  }
@@ -30237,6 +30685,7 @@ function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
30237
30685
  // preventDefault() is not allowed in passive event handler.
30238
30686
  // https://chromestatus.com/feature/6662647093133312
30239
30687
  window.addEventListener("wheel", onMouseMove, { passive: false });
30688
+ return removeListeners;
30240
30689
  }
30241
30690
  /**
30242
30691
  * Function to be used during a pointerdown event, this function allows to
@@ -30798,63 +31247,325 @@ class RoundColorPicker extends Component {
30798
31247
  }
30799
31248
  }
30800
31249
 
31250
+ css /* scss */ `
31251
+ .o-chart-title-designer {
31252
+ > span {
31253
+ height: 30px;
31254
+ }
31255
+
31256
+ .o-menu-item-button.active {
31257
+ background-color: #e6f4ea;
31258
+ color: #188038;
31259
+ }
31260
+
31261
+ .o-dropdown-content {
31262
+ overflow-y: auto;
31263
+ overflow-x: hidden;
31264
+ padding: 2px;
31265
+ z-index: 100;
31266
+ box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
31267
+
31268
+ .o-dropdown-line {
31269
+ > span {
31270
+ padding: 4px;
31271
+ }
31272
+ }
31273
+ }
31274
+ }
31275
+ `;
30801
31276
  class ChartTitle extends Component {
30802
31277
  static template = "o-spreadsheet.ChartTitle";
30803
- static components = { Section };
30804
- static props = { title: String, update: Function };
31278
+ static components = { Section, ColorPickerWidget };
31279
+ static props = {
31280
+ title: String,
31281
+ updateTitle: Function,
31282
+ name: { type: String, optional: true },
31283
+ toggleItalic: { type: Function, optional: true },
31284
+ toggleBold: { type: Function, optional: true },
31285
+ updateAlignment: { type: Function, optional: true },
31286
+ updateColor: { type: Function, optional: true },
31287
+ style: { type: Object, optional: true },
31288
+ };
31289
+ openedEl = null;
31290
+ setup() {
31291
+ useExternalListener(window, "click", this.onExternalClick);
31292
+ }
31293
+ state = useState({
31294
+ activeTool: "",
31295
+ });
30805
31296
  updateTitle(ev) {
30806
- this.props.update(ev.target.value);
31297
+ this.props.updateTitle(ev.target.value);
31298
+ }
31299
+ toggleDropdownTool(tool, ev) {
31300
+ const isOpen = this.state.activeTool === tool;
31301
+ this.closeMenus();
31302
+ this.state.activeTool = isOpen ? "" : tool;
31303
+ this.openedEl = isOpen ? null : ev.target;
31304
+ }
31305
+ /**
31306
+ * TODO: This is clearly not a goot way to handle external click, but
31307
+ * we currently have no other way to do it ... Should be done in
31308
+ * another task to handle the fact we want only one menu opened at a
31309
+ * time with something like a menuStore ?
31310
+ */
31311
+ onExternalClick(ev) {
31312
+ if (this.openedEl === ev.target) {
31313
+ return;
31314
+ }
31315
+ this.closeMenus();
31316
+ }
31317
+ onColorPicked(color) {
31318
+ this.props.updateColor?.(color);
31319
+ this.closeMenus();
31320
+ }
31321
+ updateAlignment(aligment) {
31322
+ this.props.updateAlignment?.(aligment);
31323
+ this.closeMenus();
31324
+ }
31325
+ closeMenus() {
31326
+ this.state.activeTool = "";
31327
+ this.openedEl = null;
30807
31328
  }
30808
31329
  }
30809
31330
 
30810
- class GenericChartDesignPanel extends Component {
30811
- static template = "o-spreadsheet-GenericChartDesignPanel";
30812
- static components = { RoundColorPicker, ChartTitle, Section };
31331
+ class AxisDesignEditor extends Component {
31332
+ static template = "o-spreadsheet-AxisDesignEditor";
31333
+ static components = {
31334
+ Section,
31335
+ ChartTitle,
31336
+ };
31337
+ state = useState({ currentAxis: "x" });
31338
+ get axisTitleStyle() {
31339
+ const axisDesign = this.props.definition.axesDesign?.[this.state.currentAxis] ?? {};
31340
+ return {
31341
+ color: "",
31342
+ align: "center",
31343
+ ...axisDesign.title,
31344
+ };
31345
+ }
31346
+ updateAxisTitleColor(color) {
31347
+ const axesDesign = this.props.definition.axesDesign ?? {};
31348
+ axesDesign[this.state.currentAxis] = {
31349
+ ...axesDesign[this.state.currentAxis],
31350
+ title: {
31351
+ ...(axesDesign[this.state.currentAxis]?.title ?? {}),
31352
+ color,
31353
+ },
31354
+ };
31355
+ this.props.updateChart(this.props.figureId, { axesDesign });
31356
+ }
31357
+ toggleBoldAxisTitle() {
31358
+ const axesDesign = this.props.definition.axesDesign ?? {};
31359
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31360
+ axesDesign[this.state.currentAxis] = {
31361
+ ...axesDesign[this.state.currentAxis],
31362
+ title: {
31363
+ ...title,
31364
+ bold: !title?.bold,
31365
+ },
31366
+ };
31367
+ this.props.updateChart(this.props.figureId, { axesDesign });
31368
+ }
31369
+ toggleItalicAxisTitle() {
31370
+ const axesDesign = this.props.definition.axesDesign ?? {};
31371
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31372
+ axesDesign[this.state.currentAxis] = {
31373
+ ...axesDesign[this.state.currentAxis],
31374
+ title: {
31375
+ ...title,
31376
+ italic: !title?.italic,
31377
+ },
31378
+ };
31379
+ this.props.updateChart(this.props.figureId, { axesDesign });
31380
+ }
31381
+ updateAxisTitleAlignment(align) {
31382
+ const axesDesign = this.props.definition.axesDesign ?? {};
31383
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31384
+ axesDesign[this.state.currentAxis] = {
31385
+ ...axesDesign[this.state.currentAxis],
31386
+ title: {
31387
+ ...title,
31388
+ align,
31389
+ },
31390
+ };
31391
+ this.props.updateChart(this.props.figureId, { axesDesign });
31392
+ }
31393
+ updateAxisEditor(ev) {
31394
+ const axis = ev.target.value;
31395
+ this.state.currentAxis = axis;
31396
+ }
31397
+ getAxisTitle() {
31398
+ const axesDesign = this.props.definition.axesDesign ?? {};
31399
+ return axesDesign[this.state.currentAxis]?.title.text || "";
31400
+ }
31401
+ updateAxisTitle(text) {
31402
+ const axesDesign = this.props.definition.axesDesign ?? {};
31403
+ axesDesign[this.state.currentAxis] = {
31404
+ ...axesDesign[this.state.currentAxis],
31405
+ title: {
31406
+ ...axesDesign?.[this.state.currentAxis]?.title,
31407
+ text,
31408
+ },
31409
+ };
31410
+ this.props.updateChart(this.props.figureId, { axesDesign });
31411
+ }
31412
+ }
31413
+
31414
+ class GeneralDesignEditor extends Component {
31415
+ static template = "o-spreadsheet-GeneralDesignEditor";
31416
+ static components = {
31417
+ RoundColorPicker,
31418
+ ChartTitle,
31419
+ Section,
31420
+ SidePanelCollapsible,
31421
+ };
30813
31422
  static props = {
30814
31423
  figureId: String,
30815
31424
  definition: Object,
30816
31425
  updateChart: Function,
30817
- canUpdateChart: Function,
31426
+ slots: { type: Object, optional: true },
30818
31427
  };
31428
+ state;
31429
+ setup() {
31430
+ this.state = useState({
31431
+ activeTool: "",
31432
+ });
31433
+ }
30819
31434
  get title() {
30820
- return _t(this.props.definition.title);
31435
+ return this.props.definition.title;
31436
+ }
31437
+ toggleDropdownTool(tool, ev) {
31438
+ const isOpen = this.state.activeTool === tool;
31439
+ this.state.activeTool = isOpen ? "" : tool;
30821
31440
  }
30822
31441
  updateBackgroundColor(color) {
30823
31442
  this.props.updateChart(this.props.figureId, {
30824
31443
  background: color,
30825
31444
  });
30826
31445
  }
30827
- updateTitle(title) {
31446
+ updateTitle(newTitle) {
31447
+ const title = { ...this.title, text: newTitle };
30828
31448
  this.props.updateChart(this.props.figureId, { title });
30829
31449
  }
30830
- updateSelect(attr, ev) {
30831
- this.props.updateChart(this.props.figureId, {
30832
- [attr]: ev.target.value,
30833
- });
31450
+ get titleStyle() {
31451
+ return {
31452
+ align: "left",
31453
+ ...this.title,
31454
+ };
30834
31455
  }
30835
- get backgroundColorTitle() {
30836
- return ChartTerms.BackgroundColor;
31456
+ updateChartTitleColor(color) {
31457
+ const title = { ...this.title, color };
31458
+ this.props.updateChart(this.props.figureId, { title });
31459
+ this.state.activeTool = "";
31460
+ }
31461
+ toggleBoldChartTitle() {
31462
+ let title = this.title;
31463
+ title = { ...title, bold: !title.bold };
31464
+ this.props.updateChart(this.props.figureId, { title });
31465
+ }
31466
+ toggleItalicChartTitle() {
31467
+ let title = this.title;
31468
+ title = { ...title, italic: !title.italic };
31469
+ this.props.updateChart(this.props.figureId, { title });
31470
+ }
31471
+ updateChartTitleAlignment(align) {
31472
+ const title = { ...this.title, align };
31473
+ this.props.updateChart(this.props.figureId, { title });
31474
+ this.state.activeTool = "";
30837
31475
  }
30838
31476
  }
30839
31477
 
30840
- class BarChartDesignPanel extends GenericChartDesignPanel {
30841
- static template = "o-spreadsheet-BarChartDesignPanel";
30842
- }
30843
-
30844
- class ComboChartConfigPanel extends GenericChartConfigPanel {
30845
- static template = "o-spreadsheet-ComboChartConfigPanel";
30846
- get shouldUseRightAxis() {
30847
- return _t("Use right axis for line series");
31478
+ class ChartWithAxisDesignPanel extends Component {
31479
+ static template = "o-spreadsheet-ChartWithAxisDesignPanel";
31480
+ static components = {
31481
+ GeneralDesignEditor,
31482
+ SidePanelCollapsible,
31483
+ Section,
31484
+ AxisDesignEditor,
31485
+ RoundColorPicker,
31486
+ };
31487
+ state = useState({ index: 0 });
31488
+ get axesList() {
31489
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
31490
+ let axes = [{ id: "x", name: _t("Horizontal axis") }];
31491
+ if (useLeftAxis) {
31492
+ axes.push({ id: "y", name: _t("Vertical (left) axis") });
31493
+ }
31494
+ if (useRightAxis) {
31495
+ axes.push({ id: "y1", name: _t("Vertical (right) axis") });
31496
+ }
31497
+ return axes;
30848
31498
  }
30849
- onUpdateUseRightAxis(useBothYAxis) {
31499
+ updateLegendPosition(ev) {
30850
31500
  this.props.updateChart(this.props.figureId, {
30851
- useBothYAxis,
31501
+ legendPosition: ev.target.value,
30852
31502
  });
30853
31503
  }
30854
- }
30855
-
30856
- class ComboChartDesignPanel extends GenericChartDesignPanel {
30857
- static template = "o-spreadsheet-ComboChartDesignPanel";
31504
+ getDataSeries() {
31505
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
31506
+ if (!runtime || !("chartJsConfig" in runtime)) {
31507
+ return [];
31508
+ }
31509
+ return runtime.chartJsConfig.data.datasets.map((d) => d.label);
31510
+ }
31511
+ updateSerieEditor(ev) {
31512
+ const chartId = this.props.figureId;
31513
+ const selectedIndex = ev.target.selectedIndex;
31514
+ const runtime = this.env.model.getters.getChartRuntime(chartId);
31515
+ if (!runtime) {
31516
+ return;
31517
+ }
31518
+ this.state.index = selectedIndex;
31519
+ }
31520
+ updateDataSeriesColor(color) {
31521
+ const dataSets = this.props.definition.dataSets;
31522
+ if (!dataSets?.[this.state.index])
31523
+ return;
31524
+ dataSets[this.state.index] = {
31525
+ ...dataSets[this.state.index],
31526
+ backgroundColor: color,
31527
+ };
31528
+ this.props.updateChart(this.props.figureId, { dataSets });
31529
+ }
31530
+ getDataSerieColor() {
31531
+ const dataSets = this.props.definition.dataSets;
31532
+ if (!dataSets?.[this.state.index])
31533
+ return "";
31534
+ const color = dataSets[this.state.index].backgroundColor;
31535
+ return color ? toHex(color) : getNthColor(this.state.index);
31536
+ }
31537
+ updateDataSeriesAxis(ev) {
31538
+ const axis = ev.target.value;
31539
+ const dataSets = this.props.definition.dataSets;
31540
+ if (!dataSets?.[this.state.index])
31541
+ return;
31542
+ dataSets[this.state.index] = {
31543
+ ...dataSets[this.state.index],
31544
+ yAxisId: axis === "left" ? "y" : "y1",
31545
+ };
31546
+ this.props.updateChart(this.props.figureId, { dataSets });
31547
+ }
31548
+ getDataSerieAxis() {
31549
+ const dataSets = this.props.definition.dataSets;
31550
+ if (!dataSets?.[this.state.index])
31551
+ return "left";
31552
+ return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31553
+ }
31554
+ updateDataSeriesLabel(ev) {
31555
+ const label = ev.target.value;
31556
+ const dataSets = this.props.definition.dataSets;
31557
+ if (!dataSets?.[this.state.index])
31558
+ return;
31559
+ dataSets[this.state.index] = {
31560
+ ...dataSets[this.state.index],
31561
+ label,
31562
+ };
31563
+ this.props.updateChart(this.props.figureId, { dataSets });
31564
+ }
31565
+ getDataSerieLabel() {
31566
+ const dataSets = this.props.definition.dataSets;
31567
+ return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
31568
+ }
30858
31569
  }
30859
31570
 
30860
31571
  class GaugeChartConfigPanel extends Component {
@@ -30889,7 +31600,7 @@ class GaugeChartConfigPanel extends Component {
30889
31600
  });
30890
31601
  }
30891
31602
  getDataRange() {
30892
- return this.dataRange || "";
31603
+ return { dataRange: this.dataRange || "" };
30893
31604
  }
30894
31605
  }
30895
31606
 
@@ -30931,24 +31642,24 @@ css /* scss */ `
30931
31642
  class GaugeChartDesignPanel extends Component {
30932
31643
  static template = "o-spreadsheet-GaugeChartDesignPanel";
30933
31644
  static components = {
30934
- ChartErrorSection,
30935
- RoundColorPicker,
30936
- ChartTitle,
31645
+ SidePanelCollapsible,
30937
31646
  Section,
31647
+ RoundColorPicker,
31648
+ GeneralDesignEditor,
31649
+ ChartErrorSection,
30938
31650
  };
30939
31651
  static props = {
30940
31652
  figureId: String,
30941
31653
  definition: Object,
30942
31654
  updateChart: Function,
30943
- canUpdateChart: Function,
31655
+ canUpdateChart: { type: Function, optional: true },
30944
31656
  };
30945
- state = useState({
30946
- openedMenu: undefined,
30947
- sectionRuleDispatchResult: undefined,
30948
- sectionRule: deepCopy(this.props.definition.sectionRule),
30949
- });
30950
- get title() {
30951
- return _t(this.props.definition.title);
31657
+ state;
31658
+ setup() {
31659
+ this.state = useState({
31660
+ sectionRuleDispatchResult: undefined,
31661
+ sectionRule: deepCopy(this.props.definition.sectionRule),
31662
+ });
30952
31663
  }
30953
31664
  get designErrorMessages() {
30954
31665
  const cancelledReasons = [...(this.state.sectionRuleDispatchResult?.reasons || [])];
@@ -30959,8 +31670,8 @@ class GaugeChartDesignPanel extends Component {
30959
31670
  background: color,
30960
31671
  });
30961
31672
  }
30962
- updateTitle(title) {
30963
- this.props.updateChart(this.props.figureId, { title });
31673
+ updateTitle(content) {
31674
+ this.props.updateChart(this.props.figureId, { title: { text: content } });
30964
31675
  }
30965
31676
  isRangeMinInvalid() {
30966
31677
  return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause("EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */) ||
@@ -31055,8 +31766,23 @@ class LineConfigPanel extends GenericChartConfigPanel {
31055
31766
  }
31056
31767
  }
31057
31768
 
31058
- class LineChartDesignPanel extends GenericChartDesignPanel {
31059
- static template = "o-spreadsheet-LineChartDesignPanel";
31769
+ class PieChartDesignPanel extends Component {
31770
+ static template = "o-spreadsheet-PieChartDesignPanel";
31771
+ static components = {
31772
+ GeneralDesignEditor,
31773
+ Section,
31774
+ };
31775
+ static props = {
31776
+ figureId: String,
31777
+ definition: Object,
31778
+ updateChart: Function,
31779
+ canUpdateChart: { type: Function, optional: true },
31780
+ };
31781
+ updateLegendPosition(ev) {
31782
+ this.props.updateChart(this.props.figureId, {
31783
+ legendPosition: ev.target.value,
31784
+ });
31785
+ }
31060
31786
  }
31061
31787
 
31062
31788
  class ScatterConfigPanel extends GenericChartConfigPanel {
@@ -31150,16 +31876,19 @@ class ScorecardChartConfigPanel extends Component {
31150
31876
 
31151
31877
  class ScorecardChartDesignPanel extends Component {
31152
31878
  static template = "o-spreadsheet-ScorecardChartDesignPanel";
31153
- static components = { RoundColorPicker, ChartTitle, Section, Checkbox };
31879
+ static components = {
31880
+ GeneralDesignEditor,
31881
+ RoundColorPicker,
31882
+ SidePanelCollapsible,
31883
+ Section,
31884
+ Checkbox,
31885
+ };
31154
31886
  static props = {
31155
31887
  figureId: String,
31156
31888
  definition: Object,
31157
31889
  updateChart: Function,
31158
- canUpdateChart: Function,
31890
+ canUpdateChart: { type: Function, optional: true },
31159
31891
  };
31160
- get title() {
31161
- return _t(this.props.definition.title);
31162
- }
31163
31892
  get colorsSectionTitle() {
31164
31893
  return this.props.definition.baselineMode === "progress"
31165
31894
  ? _t("Progress bar colors")
@@ -31168,8 +31897,8 @@ class ScorecardChartDesignPanel extends Component {
31168
31897
  get humanizeNumbersLabel() {
31169
31898
  return _t("Humanize numbers");
31170
31899
  }
31171
- updateTitle(title) {
31172
- this.props.updateChart(this.props.figureId, { title });
31900
+ updateTitle(content) {
31901
+ this.props.updateChart(this.props.figureId, { title: { text: content } });
31173
31902
  }
31174
31903
  updateHumanizeNumbers(humanize) {
31175
31904
  this.props.updateChart(this.props.figureId, { humanize });
@@ -31198,14 +31927,22 @@ class ScorecardChartDesignPanel extends Component {
31198
31927
  }
31199
31928
  }
31200
31929
 
31201
- class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31930
+ class WaterfallChartDesignPanel extends Component {
31202
31931
  static template = "o-spreadsheet-WaterfallChartDesignPanel";
31203
- static components = { ...GenericChartDesignPanel.components, Checkbox, RoundColorPicker };
31204
- state = useState({ pickerOpened: false });
31205
- setup() {
31206
- super.setup();
31207
- useExternalListener(window, "click", this.closePicker);
31208
- }
31932
+ static components = {
31933
+ GeneralDesignEditor,
31934
+ Checkbox,
31935
+ SidePanelCollapsible,
31936
+ Section,
31937
+ RoundColorPicker,
31938
+ AxisDesignEditor,
31939
+ };
31940
+ static props = {
31941
+ figureId: String,
31942
+ definition: Object,
31943
+ updateChart: Function,
31944
+ canUpdateChart: { type: Function, optional: true },
31945
+ };
31209
31946
  onUpdateShowSubTotals(showSubTotals) {
31210
31947
  this.props.updateChart(this.props.figureId, { showSubTotals });
31211
31948
  }
@@ -31218,11 +31955,11 @@ class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31218
31955
  updateColor(colorName, color) {
31219
31956
  this.props.updateChart(this.props.figureId, { [colorName]: color });
31220
31957
  }
31221
- closePicker() {
31222
- this.state.pickerOpened = false;
31223
- }
31224
- togglePicker() {
31225
- this.state.pickerOpened = !this.state.pickerOpened;
31958
+ get axesList() {
31959
+ return [
31960
+ { id: "x", name: _t("Horizontal axis") },
31961
+ { id: "y", name: _t("Vertical axis") },
31962
+ ];
31226
31963
  }
31227
31964
  get positiveValuesColor() {
31228
31965
  return (this.props.definition.positiveValuesColor ||
@@ -31236,29 +31973,39 @@ class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31236
31973
  return (this.props.definition.subTotalValuesColor ||
31237
31974
  CHART_WATERFALL_SUBTOTAL_COLOR);
31238
31975
  }
31976
+ updateLegendPosition(ev) {
31977
+ this.props.updateChart(this.props.figureId, {
31978
+ legendPosition: ev.target.value,
31979
+ });
31980
+ }
31981
+ updateVerticalAxisPosition(ev) {
31982
+ this.props.updateChart(this.props.figureId, {
31983
+ verticalAxisPosition: ev.target.value,
31984
+ });
31985
+ }
31239
31986
  }
31240
31987
 
31241
31988
  const chartSidePanelComponentRegistry = new Registry();
31242
31989
  chartSidePanelComponentRegistry
31243
31990
  .add("line", {
31244
31991
  configuration: LineConfigPanel,
31245
- design: LineChartDesignPanel,
31992
+ design: ChartWithAxisDesignPanel,
31246
31993
  })
31247
31994
  .add("scatter", {
31248
31995
  configuration: ScatterConfigPanel,
31249
- design: LineChartDesignPanel,
31996
+ design: ChartWithAxisDesignPanel,
31250
31997
  })
31251
31998
  .add("bar", {
31252
31999
  configuration: BarConfigPanel,
31253
- design: BarChartDesignPanel,
32000
+ design: ChartWithAxisDesignPanel,
31254
32001
  })
31255
32002
  .add("combo", {
31256
- configuration: ComboChartConfigPanel,
31257
- design: ComboChartDesignPanel,
32003
+ configuration: GenericChartConfigPanel,
32004
+ design: ChartWithAxisDesignPanel,
31258
32005
  })
31259
32006
  .add("pie", {
31260
32007
  configuration: GenericChartConfigPanel,
31261
- design: GenericChartDesignPanel,
32008
+ design: PieChartDesignPanel,
31262
32009
  })
31263
32010
  .add("gauge", {
31264
32011
  configuration: GaugeChartConfigPanel,
@@ -31544,6 +32291,7 @@ function useDragAndDropListItems() {
31544
32291
  state.itemsStyle = {};
31545
32292
  document.body.style.cursor = previousCursor;
31546
32293
  args.onCancel?.();
32294
+ cleanUp();
31547
32295
  };
31548
32296
  const onDragEnd = (itemId, indexAtEnd) => {
31549
32297
  state.draggedItemId = undefined;
@@ -31564,7 +32312,8 @@ function useDragAndDropListItems() {
31564
32312
  onDragEnd,
31565
32313
  onCancel: state.cancel,
31566
32314
  });
31567
- startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
32315
+ const stopListening = startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
32316
+ cleanupFns.push(stopListening);
31568
32317
  const onScroll = dndHelper.onScroll.bind(dndHelper);
31569
32318
  args.containerEl.addEventListener("scroll", onScroll);
31570
32319
  cleanupFns.push(() => args.containerEl.removeEventListener("scroll", onScroll));
@@ -31641,7 +32390,7 @@ class DOMDndHelper {
31641
32390
  this.moveDraggedItemToPosition(this.currentMousePosition + this.scrollOffset);
31642
32391
  }
31643
32392
  onMouseMove(ev) {
31644
- if (ev.button !== -1) {
32393
+ if (ev.button > 1) {
31645
32394
  this.onCancel();
31646
32395
  return;
31647
32396
  }
@@ -34384,7 +35133,7 @@ class SpreadsheetPivotTable {
34384
35133
  * This function converts a list of data entry into a spreadsheet pivot table.
34385
35134
  */
34386
35135
  function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
34387
- const columnsTree = dateEntriesToColumnsTree(dataEntries, definition.columns, 0);
35136
+ const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
34388
35137
  computeWidthOfColumnsNodes(columnsTree, definition.measures.length);
34389
35138
  const cols = columnsTreeToColumns(columnsTree, definition);
34390
35139
  const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
@@ -34435,7 +35184,7 @@ function dataEntriesToRows(dataEntries, index, rows, fields, values) {
34435
35184
  /**
34436
35185
  * Create the columns tree from data entries.
34437
35186
  */
34438
- function dateEntriesToColumnsTree(dataEntries, columns, index) {
35187
+ function dataEntriesToColumnsTree(dataEntries, columns, index) {
34439
35188
  if (index >= columns.length) {
34440
35189
  return [];
34441
35190
  }
@@ -34447,7 +35196,7 @@ function dateEntriesToColumnsTree(dataEntries, columns, index) {
34447
35196
  return {
34448
35197
  value,
34449
35198
  field: colName,
34450
- children: dateEntriesToColumnsTree(groups[value] || [], columns, index + 1),
35199
+ children: dataEntriesToColumnsTree(groups[value] || [], columns, index + 1),
34451
35200
  width: 0,
34452
35201
  };
34453
35202
  });
@@ -34954,7 +35703,12 @@ class SpreadsheetPivot {
34954
35703
  if (!field) {
34955
35704
  throw new Error(`Field ${this.fieldKeys[index]} does not exist`);
34956
35705
  }
34957
- entry[field.name] = cell;
35706
+ if (cell.value === "") {
35707
+ entry[field.name] = { value: null, type: CellValueType.empty };
35708
+ }
35709
+ else {
35710
+ entry[field.name] = cell;
35711
+ }
34958
35712
  }
34959
35713
  entry["__count"] = { value: 1, type: CellValueType.number };
34960
35714
  dataEntries.push(entry);
@@ -36710,14 +37464,15 @@ class FigureComponent extends Component {
36710
37464
  }
36711
37465
  onKeyDown(ev) {
36712
37466
  const figure = this.props.figure;
36713
- switch (ev.key) {
37467
+ const keyDownShortcut = keyboardEventToShortcutString(ev);
37468
+ switch (keyDownShortcut) {
36714
37469
  case "Delete":
37470
+ case "Backspace":
36715
37471
  this.env.model.dispatch("DELETE_FIGURE", {
36716
37472
  sheetId: this.env.model.getters.getActiveSheetId(),
36717
37473
  id: figure.id,
36718
37474
  });
36719
37475
  this.props.onFigureDeleted();
36720
- ev.stopPropagation();
36721
37476
  ev.preventDefault();
36722
37477
  ev.stopPropagation();
36723
37478
  break;
@@ -36738,7 +37493,22 @@ class FigureComponent extends Component {
36738
37493
  x: figure.x + delta[0],
36739
37494
  y: figure.y + delta[1],
36740
37495
  });
37496
+ ev.preventDefault();
37497
+ ev.stopPropagation();
37498
+ break;
37499
+ case "Ctrl+A":
37500
+ // Maybe in the future we will implement a way to select all figures
37501
+ ev.preventDefault();
36741
37502
  ev.stopPropagation();
37503
+ break;
37504
+ case "Ctrl+Y":
37505
+ case "Ctrl+Z":
37506
+ if (keyDownShortcut === "Ctrl+Y") {
37507
+ this.env.model.dispatch("REQUEST_REDO");
37508
+ }
37509
+ else if (keyDownShortcut === "Ctrl+Z") {
37510
+ this.env.model.dispatch("REQUEST_UNDO");
37511
+ }
36742
37512
  ev.preventDefault();
36743
37513
  ev.stopPropagation();
36744
37514
  break;
@@ -38018,9 +38788,6 @@ class FilterIcon extends Component {
38018
38788
 
38019
38789
  class FilterIconsOverlay extends Component {
38020
38790
  static template = "o-spreadsheet-FilterIconsOverlay";
38021
- static props = {
38022
- onMouseDown: Function,
38023
- };
38024
38791
  static components = {
38025
38792
  GridCellIcon,
38026
38793
  FilterIcon,
@@ -38260,6 +39027,7 @@ class GridOverlay extends Component {
38260
39027
  };
38261
39028
  gridOverlay = useRef("gridOverlay");
38262
39029
  gridOverlayRect = useAbsoluteBoundingRect(this.gridOverlay);
39030
+ cellPopovers;
38263
39031
  setup() {
38264
39032
  useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered);
38265
39033
  const resizeObserver = new ResizeObserver(() => {
@@ -38281,6 +39049,7 @@ class GridOverlay extends Component {
38281
39049
  const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
38282
39050
  return scrollY > 0;
38283
39051
  });
39052
+ this.cellPopovers = useStore(CellPopoverStore);
38284
39053
  }
38285
39054
  get gridOverlayEl() {
38286
39055
  if (!this.gridOverlay.el) {
@@ -38294,16 +39063,18 @@ class GridOverlay extends Component {
38294
39063
  get isPaintingFormat() {
38295
39064
  return this.env.model.getters.isPaintingFormat();
38296
39065
  }
38297
- onMouseDown(ev, modifiers) {
39066
+ onMouseDown(ev) {
38298
39067
  if (ev.button > 0) {
38299
39068
  // not main button, probably a context menu
38300
39069
  return;
38301
39070
  }
39071
+ if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
39072
+ this.cellPopovers.close();
39073
+ }
38302
39074
  const [col, row] = this.getCartesianCoordinates(ev);
38303
39075
  this.props.onCellClicked(col, row, {
38304
39076
  expandZone: ev.shiftKey,
38305
39077
  addZone: isCtrlKey(ev),
38306
- closePopover: modifiers?.closePopover ?? true,
38307
39078
  });
38308
39079
  }
38309
39080
  onDoubleClick(ev) {
@@ -40447,9 +41218,6 @@ class Grid extends Component {
40447
41218
  // Zone selection with mouse
40448
41219
  // ---------------------------------------------------------------------------
40449
41220
  onCellClicked(col, row, modifiers) {
40450
- if (modifiers.closePopover && this.cellPopovers.isOpen) {
40451
- this.cellPopovers.close();
40452
- }
40453
41221
  if (this.composerStore.editionMode === "editing") {
40454
41222
  this.composerStore.stopEdition();
40455
41223
  }
@@ -42322,11 +43090,20 @@ function isImageData(data) {
42322
43090
  return "imageSrc" in data;
42323
43091
  }
42324
43092
  function convertChartData(chartData) {
42325
- const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
43093
+ const dataSetsHaveTitle = chartData.dataSets.some((ds) => "reference" in (ds.label ?? {}));
42326
43094
  const labelRange = chartData.labelRange
42327
43095
  ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
42328
43096
  : undefined;
42329
- let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
43097
+ const dataSets = chartData.dataSets.map((data) => {
43098
+ let label = undefined;
43099
+ if (data.label && "text" in data.label) {
43100
+ label = data.label.text;
43101
+ }
43102
+ return {
43103
+ dataRange: convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle),
43104
+ label,
43105
+ };
43106
+ });
42330
43107
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
42331
43108
  if (chartData.type === "pie") {
42332
43109
  dataSets.reverse();
@@ -42335,10 +43112,9 @@ function convertChartData(chartData) {
42335
43112
  dataSets,
42336
43113
  dataSetsHaveTitle,
42337
43114
  labelRange,
42338
- title: chartData.title || "",
43115
+ title: chartData.title ?? { text: "" },
42339
43116
  type: chartData.type,
42340
43117
  background: convertColor({ rgb: chartData.backgroundColor }) || "#FFFFFF",
42341
- verticalAxisPosition: chartData.verticalAxisPosition,
42342
43118
  legendPosition: chartData.legendPosition,
42343
43119
  stacked: chartData.stacked || false,
42344
43120
  aggregated: false,
@@ -43453,25 +44229,20 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43453
44229
  return this.extractComboChart(rootChartElement);
43454
44230
  }
43455
44231
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
43456
- const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
44232
+ const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:chart > c:title a:t" }, (textElement) => {
43457
44233
  return textElement.textContent || "";
43458
44234
  }).join("");
43459
44235
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
43460
44236
  default: "clustered",
43461
44237
  }).asString();
43462
44238
  return {
43463
- title: chartTitle,
44239
+ title: { text: chartTitle },
43464
44240
  type: CHART_TYPE_CONVERSION_MAP[chartType],
43465
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
44241
+ dataSets: this.extractChartDatasets(this.querySelectorAll(rootChartElement, `c:${chartType}`), chartType),
43466
44242
  labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
43467
44243
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43468
44244
  default: "ffffff",
43469
44245
  }).asString(),
43470
- verticalAxisPosition: this.extractChildAttr(rootChartElement, "c:valAx > c:axPos", "val", {
43471
- default: "l",
43472
- }).asString() === "r"
43473
- ? "right"
43474
- : "left",
43475
44246
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
43476
44247
  default: "b",
43477
44248
  }).asString()],
@@ -43489,21 +44260,16 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43489
44260
  default: "clustered",
43490
44261
  }).asString();
43491
44262
  return {
43492
- title: chartTitle,
44263
+ title: { text: chartTitle },
43493
44264
  type: "combo",
43494
44265
  dataSets: [
43495
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
43496
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
44266
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:barChart`), "comboChart"),
44267
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:lineChart`), "comboChart"),
43497
44268
  ],
43498
44269
  labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
43499
44270
  backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43500
44271
  default: "ffffff",
43501
44272
  }).asString(),
43502
- verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
43503
- default: "l",
43504
- }).asString() === "r"
43505
- ? "right"
43506
- : "left",
43507
44273
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
43508
44274
  default: "b",
43509
44275
  }).asString()],
@@ -43511,21 +44277,49 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43511
44277
  fontColor: "000000",
43512
44278
  };
43513
44279
  }
43514
- extractChartDatasets(chartElement, chartType) {
43515
- if (chartType === "scatterChart") {
43516
- return this.extractScatterChartDatasets(chartElement);
43517
- }
43518
- return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
43519
- return {
43520
- label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
43521
- range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
43522
- };
43523
- });
44280
+ extractChartDatasets(chartElements, chartType) {
44281
+ return Array.from(chartElements)
44282
+ .map((element) => {
44283
+ if (chartType === "scatterChart") {
44284
+ return this.extractScatterChartDatasets(element);
44285
+ }
44286
+ return this.mapOnElements({ parent: element, query: "c:ser" }, (chartDataElement) => {
44287
+ let label = {};
44288
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44289
+ if (reference) {
44290
+ label = { reference };
44291
+ }
44292
+ else {
44293
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44294
+ if (text) {
44295
+ label = { text };
44296
+ }
44297
+ }
44298
+ return {
44299
+ label,
44300
+ range: this.extractChildTextContent(chartDataElement, "c:val c:f", {
44301
+ required: true,
44302
+ }),
44303
+ };
44304
+ });
44305
+ })
44306
+ .flat();
43524
44307
  }
43525
44308
  extractScatterChartDatasets(chartElement) {
43526
44309
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
44310
+ let label = {};
44311
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44312
+ if (reference) {
44313
+ label = { reference };
44314
+ }
44315
+ else {
44316
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44317
+ if (text) {
44318
+ label = { text };
44319
+ }
44320
+ }
43527
44321
  return {
43528
- label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
44322
+ label,
43529
44323
  range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
43530
44324
  };
43531
44325
  });
@@ -44257,7 +45051,7 @@ function getRelationFile(file, xmls) {
44257
45051
  return relsFile;
44258
45052
  }
44259
45053
 
44260
- const EXCEL_IMPORT_VERSION = 12;
45054
+ const EXCEL_IMPORT_VERSION = 16;
44261
45055
  class XlsxReader {
44262
45056
  warningManager;
44263
45057
  xmls;
@@ -44735,6 +45529,30 @@ const MIGRATIONS = [
44735
45529
  return data;
44736
45530
  },
44737
45531
  },
45532
+ {
45533
+ description: "transform chart data structure (2)",
45534
+ from: 16,
45535
+ to: 17,
45536
+ applyMigration(data) {
45537
+ for (const sheet of data.sheets || []) {
45538
+ for (const f in sheet.figures || []) {
45539
+ const figure = sheet.figures[f];
45540
+ if ("title" in figure.data && typeof figure.data.title === "string") {
45541
+ figure.data.title = { text: figure.data.title };
45542
+ }
45543
+ const figureType = figure.data.type;
45544
+ if (!["line", "bar", "pie", "scatter", "waterfall", "combo"].includes(figureType)) {
45545
+ continue;
45546
+ }
45547
+ const { dataSets, ...newData } = sheet.figures[f].data;
45548
+ const newDataSets = dataSets.map((dataRange) => ({ dataRange }));
45549
+ newData.dataSets = newDataSets;
45550
+ sheet.figures[f].data = newData;
45551
+ }
45552
+ }
45553
+ return data;
45554
+ },
45555
+ },
44738
45556
  ];
44739
45557
  /**
44740
45558
  * This function is used to repair faulty data independently of the migration.
@@ -48346,6 +49164,9 @@ class RangeAdapter {
48346
49164
  if (range.invalidXc) {
48347
49165
  return range.invalidXc;
48348
49166
  }
49167
+ if (!this.getters.tryGetSheet(range.sheetId)) {
49168
+ return CellErrorType.InvalidReference;
49169
+ }
48349
49170
  if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {
48350
49171
  return CellErrorType.InvalidReference;
48351
49172
  }
@@ -51764,7 +52585,6 @@ class PositionSet {
51764
52585
  *
51765
52586
  */
51766
52587
  class SpreadingRelation {
51767
- createEmptyPositionSet;
51768
52588
  /**
51769
52589
  * Internal structure:
51770
52590
  * For something like
@@ -51795,9 +52615,6 @@ class SpreadingRelation {
51795
52615
  */
51796
52616
  resultsToArrayFormulas = new PositionMap();
51797
52617
  arrayFormulasToResults = new PositionMap();
51798
- constructor(createEmptyPositionSet) {
51799
- this.createEmptyPositionSet = createEmptyPositionSet;
51800
- }
51801
52618
  getFormulaPositionsSpreadingOn(resultPosition) {
51802
52619
  return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
51803
52620
  }
@@ -51816,13 +52633,13 @@ class SpreadingRelation {
51816
52633
  */
51817
52634
  addRelation({ arrayFormulaPosition, resultPosition, }) {
51818
52635
  if (!this.resultsToArrayFormulas.has(resultPosition)) {
51819
- this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
52636
+ this.resultsToArrayFormulas.set(resultPosition, []);
51820
52637
  }
51821
- this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
52638
+ this.resultsToArrayFormulas.get(resultPosition)?.push(arrayFormulaPosition);
51822
52639
  if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
51823
- this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
52640
+ this.arrayFormulasToResults.set(arrayFormulaPosition, []);
51824
52641
  }
51825
- this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
52642
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.push(resultPosition);
51826
52643
  }
51827
52644
  hasArrayFormulaResult(position) {
51828
52645
  return this.resultsToArrayFormulas.has(position);
@@ -51843,7 +52660,7 @@ class Evaluator {
51843
52660
  evaluatedCells = new PositionMap();
51844
52661
  formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
51845
52662
  blockedArrayFormulas = new PositionSet({});
51846
- spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52663
+ spreadingRelations = new SpreadingRelation();
51847
52664
  constructor(context, getters) {
51848
52665
  this.context = context;
51849
52666
  this.getters = getters;
@@ -51937,7 +52754,7 @@ class Evaluator {
51937
52754
  }
51938
52755
  buildDependencyGraph() {
51939
52756
  this.blockedArrayFormulas = this.createEmptyPositionSet();
51940
- this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52757
+ this.spreadingRelations = new SpreadingRelation();
51941
52758
  this.formulaDependencies = lazy(() => {
51942
52759
  const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
51943
52760
  .filter((range) => !range.invalidSheetName && !range.invalidXc)
@@ -52069,6 +52886,7 @@ class Evaluator {
52069
52886
  const nbColumns = formulaReturn.length;
52070
52887
  const nbRows = formulaReturn[0].length;
52071
52888
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52889
+ this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52072
52890
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
52073
52891
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
52074
52892
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
@@ -52091,6 +52909,18 @@ class Evaluator {
52091
52909
  }
52092
52910
  throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
52093
52911
  }
52912
+ assertNoMergedCellsInSpreadZone({ sheetId, col, row }, matrixResult) {
52913
+ const mergedCells = this.getters.getMergesInZone(sheetId, {
52914
+ top: row,
52915
+ bottom: row + matrixResult.length,
52916
+ left: col,
52917
+ right: col + matrixResult[0].length,
52918
+ });
52919
+ if (mergedCells.length === 0) {
52920
+ return;
52921
+ }
52922
+ throw new SplillBlockedError(_t("Merged cells found in the spill zone. Please unmerge cells before using array formulas."));
52923
+ }
52094
52924
  updateSpreadRelation({ sheetId, col, row, }) {
52095
52925
  const arrayFormulaPosition = { sheetId, col, row };
52096
52926
  return (i, j) => {
@@ -52328,10 +53158,6 @@ class EvaluationPlugin extends UIPlugin {
52328
53158
  this.evaluator.updateDependencies(cmd);
52329
53159
  }
52330
53160
  break;
52331
- case "DUPLICATE_SHEET":
52332
- case "CREATE_SHEET":
52333
- this.shouldRebuildDependenciesGraph = true;
52334
- break;
52335
53161
  case "EVALUATE_CELLS":
52336
53162
  this.evaluator.evaluateAllCells();
52337
53163
  break;
@@ -52432,16 +53258,22 @@ class EvaluationPlugin extends UIPlugin {
52432
53258
  let newContent = undefined;
52433
53259
  let newFormat = undefined;
52434
53260
  let isExported = true;
53261
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52435
53262
  const formulaCell = this.getCorrespondingFormulaCell(position);
52436
53263
  if (formulaCell) {
52437
53264
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
52438
53265
  isFormula = isExported;
52439
53266
  if (!isExported) {
52440
- newContent = (value ?? "").toString();
52441
- newFormat = evaluatedCell.format;
53267
+ // If the cell contains a non-exported formula and that is evaluates to
53268
+ // nothing* ,we don't export it.
53269
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
53270
+ // the empty string.
53271
+ if (value !== "") {
53272
+ newContent = (value ?? "").toString();
53273
+ newFormat = evaluatedCell.format;
53274
+ }
52442
53275
  }
52443
53276
  }
52444
- const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52445
53277
  const exportedCellData = exportedSheetData.cells[xc] || {};
52446
53278
  const format = newFormat
52447
53279
  ? getItemId(newFormat, data.formats)
@@ -57064,7 +57896,7 @@ class ClipboardPlugin extends UIPlugin {
57064
57896
  paintFormatStatus = "inactive";
57065
57897
  originSheetId;
57066
57898
  copiedData;
57067
- _isCutOperation;
57899
+ _isCutOperation = false;
57068
57900
  // ---------------------------------------------------------------------------
57069
57901
  // Command Handling
57070
57902
  // ---------------------------------------------------------------------------
@@ -57076,14 +57908,17 @@ class ClipboardPlugin extends UIPlugin {
57076
57908
  case "PASTE_FROM_OS_CLIPBOARD": {
57077
57909
  const copiedData = this.convertOSClipboardData(cmd.text);
57078
57910
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57079
- return this.isPasteAllowed(cmd.target, copiedData, { pasteOption });
57911
+ return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
57080
57912
  }
57081
57913
  case "PASTE": {
57082
57914
  if (!this.copiedData) {
57083
57915
  return "EmptyClipboard" /* CommandResult.EmptyClipboard */;
57084
57916
  }
57085
57917
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57086
- return this.isPasteAllowed(cmd.target, this.copiedData, { pasteOption });
57918
+ return this.isPasteAllowed(cmd.target, this.copiedData, {
57919
+ pasteOption: pasteOption,
57920
+ isCutOperation: this._isCutOperation,
57921
+ });
57087
57922
  }
57088
57923
  case "COPY_PASTE_CELLS_ABOVE": {
57089
57924
  const zones = this.getters.getSelectedZones();
@@ -57101,13 +57936,13 @@ class ClipboardPlugin extends UIPlugin {
57101
57936
  }
57102
57937
  case "INSERT_CELL": {
57103
57938
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57104
- const copiedData = this.copy("CUT", cut);
57105
- return this.isPasteAllowed(paste, copiedData, {});
57939
+ const copiedData = this.copy(cut);
57940
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57106
57941
  }
57107
57942
  case "DELETE_CELL": {
57108
57943
  const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
57109
- const copiedData = this.copy("CUT", cut);
57110
- return this.isPasteAllowed(paste, copiedData, {});
57944
+ const copiedData = this.copy(cut);
57945
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57111
57946
  }
57112
57947
  case "ACTIVATE_PAINT_FORMAT": {
57113
57948
  if (this.paintFormatStatus !== "inactive") {
@@ -57125,23 +57960,27 @@ class ClipboardPlugin extends UIPlugin {
57125
57960
  const zones = this.getters.getSelectedZones();
57126
57961
  this.status = "visible";
57127
57962
  this.originSheetId = this.getters.getActiveSheetId();
57128
- this.copiedData = this.copy(cmd.type, zones);
57963
+ this.copiedData = this.copy(zones);
57964
+ this._isCutOperation = cmd.type === "CUT";
57129
57965
  break;
57130
57966
  case "PASTE_FROM_OS_CLIPBOARD": {
57967
+ this._isCutOperation = false;
57131
57968
  this.copiedData = this.convertOSClipboardData(cmd.text);
57132
57969
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57133
- this.paste(cmd.target, {
57970
+ this.paste(cmd.target, this.copiedData, {
57134
57971
  pasteOption,
57135
57972
  selectTarget: true,
57973
+ isCutOperation: false,
57136
57974
  });
57137
57975
  this.status = "invisible";
57138
57976
  break;
57139
57977
  }
57140
57978
  case "PASTE": {
57141
57979
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57142
- this.paste(cmd.target, {
57980
+ this.paste(cmd.target, this.copiedData, {
57143
57981
  pasteOption,
57144
57982
  selectTarget: true,
57983
+ isCutOperation: this._isCutOperation,
57145
57984
  });
57146
57985
  if (this.paintFormatStatus === "oneOff") {
57147
57986
  this.paintFormatStatus = "inactive";
@@ -57162,9 +58001,9 @@ class ClipboardPlugin extends UIPlugin {
57162
58001
  top: multipleRowsInSelection ? zone.top : zone.top - 1,
57163
58002
  };
57164
58003
  this.originSheetId = this.getters.getActiveSheetId();
57165
- this.copiedData = this.copy("COPY", [copyTarget]);
57166
- this.paste([zone], {
57167
- pasteOption: undefined,
58004
+ const copiedData = this.copy([copyTarget]);
58005
+ this.paste([zone], copiedData, {
58006
+ isCutOperation: false,
57168
58007
  selectTarget: true,
57169
58008
  });
57170
58009
  }
@@ -57179,9 +58018,9 @@ class ClipboardPlugin extends UIPlugin {
57179
58018
  left: multipleColsInSelection ? zone.left : zone.left - 1,
57180
58019
  };
57181
58020
  this.originSheetId = this.getters.getActiveSheetId();
57182
- this.copiedData = this.copy("COPY", [copyTarget]);
57183
- this.paste([zone], {
57184
- pasteOption: undefined,
58021
+ const copiedData = this.copy([copyTarget]);
58022
+ this.paste([zone], copiedData, {
58023
+ isCutOperation: false,
57185
58024
  selectTarget: true,
57186
58025
  });
57187
58026
  }
@@ -57197,14 +58036,14 @@ class ClipboardPlugin extends UIPlugin {
57197
58036
  }
57198
58037
  break;
57199
58038
  }
57200
- this.copiedData = this.copy("CUT", cut);
57201
- this.paste(paste, {});
58039
+ const copiedData = this.copy(cut);
58040
+ this.paste(paste, copiedData, { isCutOperation: true });
57202
58041
  break;
57203
58042
  }
57204
58043
  case "INSERT_CELL": {
57205
58044
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57206
- this.copiedData = this.copy("CUT", cut);
57207
- this.paste(paste, {});
58045
+ const copiedData = this.copy(cut);
58046
+ this.paste(paste, copiedData, { isCutOperation: true });
57208
58047
  break;
57209
58048
  }
57210
58049
  case "ADD_COLUMNS_ROWS": {
@@ -57236,7 +58075,8 @@ class ClipboardPlugin extends UIPlugin {
57236
58075
  break;
57237
58076
  }
57238
58077
  case "REPEAT_PASTE": {
57239
- this.paste(cmd.target, {
58078
+ this.paste(cmd.target, this.copiedData, {
58079
+ isCutOperation: false,
57240
58080
  pasteOption: cmd.pasteOption,
57241
58081
  selectTarget: true,
57242
58082
  });
@@ -57244,7 +58084,7 @@ class ClipboardPlugin extends UIPlugin {
57244
58084
  }
57245
58085
  case "ACTIVATE_PAINT_FORMAT": {
57246
58086
  const zones = this.getters.getSelectedZones();
57247
- this.copiedData = this.copy("COPY", zones);
58087
+ this.copiedData = this.copy(zones);
57248
58088
  this.status = "visible";
57249
58089
  if (cmd.persistent) {
57250
58090
  this.paintFormatStatus = "persistent";
@@ -57275,7 +58115,6 @@ class ClipboardPlugin extends UIPlugin {
57275
58115
  }
57276
58116
  }
57277
58117
  convertOSClipboardData(clipboardData) {
57278
- this._isCutOperation = false;
57279
58118
  const handlers = clipboardHandlersRegistries.figureHandlers
57280
58119
  .getAll()
57281
58120
  .map((handler) => new handler(this.getters, this.dispatch));
@@ -57313,7 +58152,6 @@ class ClipboardPlugin extends UIPlugin {
57313
58152
  for (const handler of this.selectClipboardHandlers(copiedData)) {
57314
58153
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
57315
58154
  ...options,
57316
- isCutOperation: this.isCutOperation(),
57317
58155
  });
57318
58156
  if (result !== "Success" /* CommandResult.Success */) {
57319
58157
  return result;
@@ -57336,9 +58174,8 @@ class ClipboardPlugin extends UIPlugin {
57336
58174
  }
57337
58175
  return false;
57338
58176
  }
57339
- copy(operation, zones) {
58177
+ copy(zones) {
57340
58178
  let copiedData = {};
57341
- this._isCutOperation = operation === "CUT";
57342
58179
  const clipboardData = this.getClipboardData(zones);
57343
58180
  for (const handler of this.selectClipboardHandlers(clipboardData)) {
57344
58181
  const data = handler.copy(clipboardData);
@@ -57346,8 +58183,8 @@ class ClipboardPlugin extends UIPlugin {
57346
58183
  }
57347
58184
  return copiedData;
57348
58185
  }
57349
- paste(zones, options) {
57350
- if (!this.copiedData) {
58186
+ paste(zones, copiedData, options) {
58187
+ if (!copiedData) {
57351
58188
  return;
57352
58189
  }
57353
58190
  let zone = undefined;
@@ -57355,12 +58192,9 @@ class ClipboardPlugin extends UIPlugin {
57355
58192
  let target = {
57356
58193
  zones,
57357
58194
  };
57358
- const handlers = this.selectClipboardHandlers(this.copiedData);
58195
+ const handlers = this.selectClipboardHandlers(copiedData);
57359
58196
  for (const handler of handlers) {
57360
- const currentTarget = handler.getPasteTarget(zones, this.copiedData, {
57361
- ...options,
57362
- isCutOperation: this.isCutOperation(),
57363
- });
58197
+ const currentTarget = handler.getPasteTarget(zones, copiedData, options);
57364
58198
  if (currentTarget.figureId) {
57365
58199
  target.figureId = currentTarget.figureId;
57366
58200
  }
@@ -57376,7 +58210,7 @@ class ClipboardPlugin extends UIPlugin {
57376
58210
  if (zone !== undefined) {
57377
58211
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
57378
58212
  }
57379
- handlers.forEach((handler) => handler.paste(target, this.copiedData, { ...options, isCutOperation: this.isCutOperation() }));
58213
+ handlers.forEach((handler) => handler.paste(target, copiedData, options));
57380
58214
  if (!options?.selectTarget) {
57381
58215
  return;
57382
58216
  }
@@ -59762,6 +60596,7 @@ class BottomBarSheet extends Component {
59762
60596
  sheetDivRef = useRef("sheetDiv");
59763
60597
  sheetNameRef = useRef("sheetNameSpan");
59764
60598
  editionState = "initializing";
60599
+ DOMFocusableElementStore;
59765
60600
  setup() {
59766
60601
  onMounted(() => {
59767
60602
  if (this.isSheetActive) {
@@ -59774,6 +60609,7 @@ class BottomBarSheet extends Component {
59774
60609
  this.focusInputAndSelectContent();
59775
60610
  }
59776
60611
  });
60612
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
59777
60613
  }
59778
60614
  focusInputAndSelectContent() {
59779
60615
  if (!this.state.isEditing || !this.sheetNameRef.el)
@@ -59815,9 +60651,11 @@ class BottomBarSheet extends Component {
59815
60651
  if (ev.key === "Enter") {
59816
60652
  ev.preventDefault();
59817
60653
  this.stopEdition();
60654
+ this.DOMFocusableElementStore.focus();
59818
60655
  }
59819
60656
  if (ev.key === "Escape") {
59820
60657
  this.cancelEdition();
60658
+ this.DOMFocusableElementStore.focus();
59821
60659
  }
59822
60660
  }
59823
60661
  onClickSheetName(ev) {
@@ -61866,6 +62704,9 @@ class Spreadsheet extends Component {
61866
62704
  static template = "o-spreadsheet-Spreadsheet";
61867
62705
  static props = {
61868
62706
  model: Object,
62707
+ notifyUser: { type: Function, optional: true },
62708
+ raiseError: { type: Function, optional: true },
62709
+ askConfirmation: { type: Function, optional: true },
61869
62710
  };
61870
62711
  static components = {
61871
62712
  TopBar,
@@ -61912,7 +62753,11 @@ class Spreadsheet extends Component {
61912
62753
  toggleSidePanel: this.sidePanel.toggle.bind(this.sidePanel),
61913
62754
  clipboard: this.env.clipboard || instantiateClipboard(),
61914
62755
  startCellEdition: (content) => this.composerFocusStore.focusGridComposerCell(content),
62756
+ notifyUser: (notification) => this.notificationStore.notifyUser(notification),
62757
+ askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
62758
+ raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
61915
62759
  });
62760
+ this.notificationStore.updateNotificationCallbacks({ ...this.props });
61916
62761
  useEffect(() => {
61917
62762
  /**
61918
62763
  * Only refocus the grid if the active element is not a child of the spreadsheet
@@ -61935,6 +62780,9 @@ class Spreadsheet extends Component {
61935
62780
  if (nextProps.model !== this.props.model) {
61936
62781
  throw new Error("Changing the props model is not supported at the moment.");
61937
62782
  }
62783
+ if (!deepEquals(nextProps, this.props)) {
62784
+ this.notificationStore.updateNotificationCallbacks({ ...nextProps });
62785
+ }
61938
62786
  });
61939
62787
  const render = batched(this.render.bind(this, true));
61940
62788
  onMounted(() => {
@@ -61952,7 +62800,7 @@ class Spreadsheet extends Component {
61952
62800
  bindModelEvents() {
61953
62801
  this.model.on("update", this, () => this.render(true));
61954
62802
  this.model.on("notify-ui", this, (notification) => this.notificationStore.notifyUser(notification));
61955
- this.model.on("raise-error-ui", this, ({ text }) => this.env.raiseError(text));
62803
+ this.model.on("raise-error-ui", this, ({ text }) => this.notificationStore.raiseError(text));
61956
62804
  }
61957
62805
  unbindModelEvents() {
61958
62806
  this.model.off("update", this);
@@ -63507,10 +64355,13 @@ function createChart(chart, chartSheetIndex, data) {
63507
64355
  });
63508
64356
  // <manualLayout/> to manually position the chart in the figure container
63509
64357
  let title = escapeXml ``;
63510
- if (chart.data.title) {
64358
+ if (chart.data.title?.text) {
64359
+ const color = chart.data.title.color
64360
+ ? toXlsxHexColor(chart.data.title.color)
64361
+ : chart.data.fontColor;
63511
64362
  title = escapeXml /*xml*/ `
63512
64363
  <c:title>
63513
- ${insertText(chart.data.title, chart.data.fontColor)}
64364
+ ${insertText(chart.data.title.text, color, DEFAULT_CHART_FONT_SIZE, chart.data.title)}
63514
64365
  <c:overlay val="0" />
63515
64366
  </c:title>
63516
64367
  `;
@@ -63598,7 +64449,7 @@ function lineAttributes(params) {
63598
64449
  </a:ln>
63599
64450
  `;
63600
64451
  }
63601
- function insertText(text, fontColor = "000000", fontsize = 22) {
64452
+ function insertText(text, fontColor = "000000", fontsize = DEFAULT_CHART_FONT_SIZE, style = {}) {
63602
64453
  return escapeXml /*xml*/ `
63603
64454
  <c:tx>
63604
64455
  <c:rich>
@@ -63606,13 +64457,13 @@ function insertText(text, fontColor = "000000", fontsize = 22) {
63606
64457
  <a:lstStyle />
63607
64458
  <a:p>
63608
64459
  <a:pPr lvl="0">
63609
- <a:defRPr b="0">
64460
+ <a:defRPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}">
63610
64461
  ${solidFill(fontColor)}
63611
64462
  <a:latin typeface="+mn-lt"/>
63612
64463
  </a:defRPr>
63613
64464
  </a:pPr>
63614
64465
  <a:r> <!-- Runs -->
63615
- <a:rPr sz="${fontsize * 100}"/>
64466
+ <a:rPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}" sz="${fontsize * 100}"/>
63616
64467
  <a:t>${text}</a:t>
63617
64468
  </a:r>
63618
64469
  </a:p>
@@ -63641,6 +64492,24 @@ function insertTextProperties(fontsize = 12, fontColor = "000000", bold = false,
63641
64492
  </c:txPr>
63642
64493
  `;
63643
64494
  }
64495
+ function extractDataSetLabel(label) {
64496
+ if (!label) {
64497
+ return escapeXml /*xml*/ ``;
64498
+ }
64499
+ if ("text" in label && label.text) {
64500
+ return escapeXml /*xml*/ `
64501
+ <c:tx><c:v>${label.text}</c:v></c:tx>
64502
+ `;
64503
+ }
64504
+ if ("reference" in label && label.reference) {
64505
+ return escapeXml /*xml*/ `
64506
+ <c:tx>
64507
+ ${stringRef(label.reference)}
64508
+ </c:tx>
64509
+ `;
64510
+ }
64511
+ return escapeXml /*xml*/ ``;
64512
+ }
63644
64513
  function addBarChart(chart) {
63645
64514
  // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
63646
64515
  // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
@@ -63648,46 +64517,72 @@ function addBarChart(chart) {
63648
64517
  //
63649
64518
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63650
64519
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
63651
- const colors = new ChartColors();
63652
- const dataSetsNodes = [];
64520
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64521
+ const colors = new ColorGenerator(dataSetsColors);
64522
+ const leftDataSetsNodes = [];
64523
+ const rightDataSetsNodes = [];
63653
64524
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63654
64525
  const color = toXlsxHexColor(colors.next());
63655
64526
  const dataShapeProperty = shapeProperty({
63656
64527
  backgroundColor: color,
63657
64528
  line: { color },
63658
64529
  });
63659
- dataSetsNodes.push(escapeXml /*xml*/ `
64530
+ const dataSetNode = escapeXml /*xml*/ `
63660
64531
  <c:ser>
63661
64532
  <c:idx val="${dsIndex}"/>
63662
64533
  <c:order val="${dsIndex}"/>
63663
- ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64534
+ ${extractDataSetLabel(dataset.label)}
63664
64535
  ${dataShapeProperty}
63665
64536
  ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63666
64537
  <c:val> <!-- x-coordinate values -->
63667
64538
  ${numberRef(dataset.range)}
63668
64539
  </c:val>
63669
64540
  </c:ser>
63670
- `);
64541
+ `;
64542
+ if (dataset.rightYAxis) {
64543
+ rightDataSetsNodes.push(dataSetNode);
64544
+ }
64545
+ else {
64546
+ leftDataSetsNodes.push(dataSetNode);
64547
+ }
63671
64548
  }
63672
- // Excel does not support this feature
63673
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63674
64549
  const grouping = chart.stacked ? "stacked" : "clustered";
63675
64550
  const overlap = chart.stacked ? 100 : -20;
63676
64551
  return escapeXml /*xml*/ `
63677
- <c:barChart>
63678
- <c:barDir val="col"/>
63679
- <c:grouping val="${grouping}"/>
63680
- <c:overlap val="${overlap}"/>
63681
- <c:gapWidth val="70"/>
63682
- <!-- each data marker in the series does not have a different color -->
63683
- <c:varyColors val="0"/>
63684
- ${joinXmlNodes(dataSetsNodes)}
63685
- <c:axId val="${catAxId}" />
63686
- <c:axId val="${valAxId}" />
63687
- </c:barChart>
63688
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63689
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
63690
- `;
64552
+ ${leftDataSetsNodes.length
64553
+ ? escapeXml /*xml*/ `
64554
+ <c:barChart>
64555
+ <c:barDir val="col"/>
64556
+ <c:grouping val="${grouping}"/>
64557
+ <c:overlap val="${overlap}"/>
64558
+ <c:gapWidth val="70"/>
64559
+ <!-- each data marker in the series does not have a different color -->
64560
+ <c:varyColors val="0"/>
64561
+ ${joinXmlNodes(leftDataSetsNodes)}
64562
+ <c:axId val="${catAxId}" />
64563
+ <c:axId val="${valAxId}" />
64564
+ </c:barChart>
64565
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64566
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64567
+ `
64568
+ : ""}
64569
+ ${rightDataSetsNodes.length
64570
+ ? escapeXml /*xml*/ `
64571
+ <c:barChart>
64572
+ <c:barDir val="col"/>
64573
+ <c:grouping val="${grouping}"/>
64574
+ <c:overlap val="${overlap}"/>
64575
+ <c:gapWidth val="70"/>
64576
+ <!-- each data marker in the series does not have a different color -->
64577
+ <c:varyColors val="0"/>
64578
+ ${joinXmlNodes(rightDataSetsNodes)}
64579
+ <c:axId val="${catAxId + 1}" />
64580
+ <c:axId val="${valAxId + 1}" />
64581
+ </c:barChart>
64582
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64583
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64584
+ `
64585
+ : ""}`;
63691
64586
  }
63692
64587
  function addComboChart(chart) {
63693
64588
  // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
@@ -63696,28 +64591,38 @@ function addComboChart(chart) {
63696
64591
  //
63697
64592
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63698
64593
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
63699
- const colors = new ChartColors();
63700
- const dataSetsNodes = [];
63701
- for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
64594
+ const dataSets = chart.dataSets;
64595
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
64596
+ const colors = new ColorGenerator(dataSetsColors);
64597
+ let dataSet = dataSets[0];
64598
+ const firstColor = toXlsxHexColor(colors.next());
64599
+ const useRightAxisForBarSerie = dataSet.rightYAxis ?? false;
64600
+ const barDataSetNode = escapeXml /*xml*/ `
64601
+ <c:ser>
64602
+ <c:idx val="0"/>
64603
+ <c:order val="0"/>
64604
+ ${extractDataSetLabel(dataSet.label)}
64605
+ ${shapeProperty({
64606
+ backgroundColor: firstColor,
64607
+ line: { color: firstColor },
64608
+ })}
64609
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64610
+ <!-- x-coordinate values -->
64611
+ <c:val>
64612
+ ${numberRef(dataSet.range)}
64613
+ </c:val>
64614
+ </c:ser>
64615
+ `;
64616
+ const leftDataSetsNodes = [];
64617
+ const rightDataSetsNodes = [];
64618
+ for (let dsIndex = 1; dsIndex < dataSets.length; dsIndex++) {
64619
+ dataSet = dataSets[dsIndex];
63702
64620
  const color = toXlsxHexColor(colors.next());
63703
64621
  const dataShapeProperty = shapeProperty({
63704
64622
  backgroundColor: color,
63705
64623
  line: { color },
63706
64624
  });
63707
- dataSetsNodes.push(dsIndex === "0"
63708
- ? escapeXml /*xml*/ `
63709
- <c:ser>
63710
- <c:idx val="${dsIndex}"/>
63711
- <c:order val="${dsIndex}"/>
63712
- ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
63713
- ${dataShapeProperty}
63714
- ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63715
- <c:val> <!-- x-coordinate values -->
63716
- ${numberRef(dataset.range)}
63717
- </c:val>
63718
- </c:ser>
63719
- `
63720
- : escapeXml /*xml*/ `
64625
+ const dataSetNode = escapeXml /*xml*/ `
63721
64626
  <c:ser>
63722
64627
  <c:idx val="${dsIndex}"/>
63723
64628
  <c:order val="${dsIndex}"/>
@@ -63725,18 +64630,24 @@ function addComboChart(chart) {
63725
64630
  <c:marker>
63726
64631
  <c:symbol val="circle" />
63727
64632
  <c:size val="5"/>
64633
+ ${dataShapeProperty}
63728
64634
  </c:marker>
63729
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64635
+ ${extractDataSetLabel(dataSet.label)}
63730
64636
  ${dataShapeProperty}
63731
- ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63732
- <c:val> <!-- x-coordinate values -->
63733
- ${numberRef(dataset.range)}
64637
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64638
+ <!-- x-coordinate values -->
64639
+ <c:val>
64640
+ ${numberRef(dataSet.range)}
63734
64641
  </c:val>
63735
64642
  </c:ser>
63736
- `);
64643
+ `;
64644
+ if (dataSet.rightYAxis) {
64645
+ rightDataSetsNodes.push(dataSetNode);
64646
+ }
64647
+ else {
64648
+ leftDataSetsNodes.push(dataSetNode);
64649
+ }
63737
64650
  }
63738
- // Excel does not support this feature
63739
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63740
64651
  const overlap = chart.stacked ? 100 : -20;
63741
64652
  return escapeXml /*xml*/ `
63742
64653
  <c:barChart>
@@ -63746,34 +64657,63 @@ function addComboChart(chart) {
63746
64657
  <c:gapWidth val="70"/>
63747
64658
  <!-- each data marker in the series does not have a different color -->
63748
64659
  <c:varyColors val="0"/>
63749
- ${dataSetsNodes[0]}
63750
- <c:axId val="${catAxId}" />
63751
- <c:axId val="${valAxId}" />
64660
+ ${barDataSetNode}
64661
+ <c:axId val="${catAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
64662
+ <c:axId val="${valAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
63752
64663
  </c:barChart>
63753
- <c:lineChart>
63754
- <c:grouping val="standard"/>
63755
- <!-- each data marker in the series does not have a different color -->
63756
- <c:varyColors val="0"/>
63757
- ${joinXmlNodes(dataSetsNodes.slice(1))}
63758
- <c:axId val="${catAxId}" />
63759
- <c:axId val="${valAxId}" />
63760
- </c:lineChart>
63761
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63762
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
64664
+ ${leftDataSetsNodes.length
64665
+ ? escapeXml /*xml*/ `
64666
+ <c:lineChart>
64667
+ <c:grouping val="standard"/>
64668
+ <!-- each data marker in the series does not have a different color -->
64669
+ <c:varyColors val="0"/>
64670
+ ${joinXmlNodes(leftDataSetsNodes)}
64671
+ <c:axId val="${catAxId}" />
64672
+ <c:axId val="${valAxId}" />
64673
+ </c:lineChart>
64674
+ `
64675
+ : ""}
64676
+ ${rightDataSetsNodes.length
64677
+ ? escapeXml /*xml*/ `
64678
+ <c:lineChart>
64679
+ <c:grouping val="standard"/>
64680
+ <!-- each data marker in the series does not have a different color -->
64681
+ <c:varyColors val="0"/>
64682
+ ${joinXmlNodes(rightDataSetsNodes)}
64683
+ <c:axId val="${catAxId + 1}" />
64684
+ <c:axId val="${valAxId + 1}" />
64685
+ </c:lineChart>
64686
+ `
64687
+ : ""}
64688
+ ${!useRightAxisForBarSerie || leftDataSetsNodes.length
64689
+ ? escapeXml /*xml*/ `
64690
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64691
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64692
+ `
64693
+ : ""}
64694
+ ${useRightAxisForBarSerie || rightDataSetsNodes.length
64695
+ ? escapeXml /*xml*/ `
64696
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length || !useRightAxisForBarSerie ? 1 : 0)}
64697
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64698
+ `
64699
+ : ""}
63763
64700
  `;
63764
64701
  }
63765
64702
  function addLineChart(chart) {
63766
- const colors = new ChartColors();
63767
- const dataSetsNodes = [];
64703
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64704
+ const colors = new ColorGenerator(dataSetsColors);
64705
+ const leftDataSetsNodes = [];
64706
+ const rightDataSetsNodes = [];
63768
64707
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
64708
+ const color = toXlsxHexColor(colors.next());
63769
64709
  const dataShapeProperty = shapeProperty({
63770
64710
  line: {
63771
64711
  width: 2.5,
63772
64712
  style: "solid",
63773
- color: toXlsxHexColor(colors.next()),
64713
+ color,
63774
64714
  },
63775
64715
  });
63776
- dataSetsNodes.push(escapeXml /*xml*/ `
64716
+ const dataSetNode = escapeXml /*xml*/ `
63777
64717
  <c:ser>
63778
64718
  <c:idx val="${dsIndex}"/>
63779
64719
  <c:order val="${dsIndex}"/>
@@ -63781,37 +64721,63 @@ function addLineChart(chart) {
63781
64721
  <c:marker>
63782
64722
  <c:symbol val="circle" />
63783
64723
  <c:size val="5"/>
64724
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63784
64725
  </c:marker>
63785
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64726
+ ${extractDataSetLabel(dataset.label)}
63786
64727
  ${dataShapeProperty}
63787
64728
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63788
64729
  <c:val> <!-- x-coordinate values -->
63789
64730
  ${numberRef(dataset.range)}
63790
64731
  </c:val>
63791
64732
  </c:ser>
63792
- `);
64733
+ `;
64734
+ if (dataset.rightYAxis) {
64735
+ rightDataSetsNodes.push(dataSetNode);
64736
+ }
64737
+ else {
64738
+ leftDataSetsNodes.push(dataSetNode);
64739
+ }
63793
64740
  }
63794
- // Excel does not support this feature
63795
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63796
64741
  const grouping = chart.stacked ? "stacked" : "standard";
63797
64742
  return escapeXml /*xml*/ `
63798
- <c:lineChart>
63799
- <c:grouping val="${grouping}"/>
63800
- <!-- each data marker in the series does not have a different color -->
63801
- <c:varyColors val="0"/>
63802
- ${joinXmlNodes(dataSetsNodes)}
63803
- <c:axId val="${catAxId}" />
63804
- <c:axId val="${valAxId}" />
63805
- </c:lineChart>
63806
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63807
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
64743
+ ${leftDataSetsNodes.length
64744
+ ? escapeXml /*xml*/ `
64745
+ <c:lineChart>
64746
+ <c:grouping val="${grouping}"/>
64747
+ <!-- each data marker in the series does not have a different color -->
64748
+ <c:varyColors val="0"/>
64749
+ ${joinXmlNodes(leftDataSetsNodes)}
64750
+ <c:axId val="${catAxId}" />
64751
+ <c:axId val="${valAxId}" />
64752
+ </c:lineChart>
64753
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64754
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64755
+ `
64756
+ : ""}
64757
+ ${rightDataSetsNodes.length
64758
+ ? escapeXml /*xml*/ `
64759
+ <c:lineChart>
64760
+ <c:grouping val="${grouping}"/>
64761
+ <!-- each data marker in the series does not have a different color -->
64762
+ <c:varyColors val="0"/>
64763
+ ${joinXmlNodes(rightDataSetsNodes)}
64764
+ <c:axId val="${catAxId + 1}" />
64765
+ <c:axId val="${valAxId + 1}" />
64766
+ </c:lineChart>
64767
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64768
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64769
+ `
64770
+ : ""}
63808
64771
  `;
63809
64772
  }
63810
64773
  function addScatterChart(chart) {
63811
- const colors = new ChartColors();
63812
- const dataSetsNodes = [];
64774
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64775
+ const colors = new ColorGenerator(dataSetsColors);
64776
+ const leftDataSetsNodes = [];
64777
+ const rightDataSetsNodes = [];
63813
64778
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63814
- dataSetsNodes.push(escapeXml /*xml*/ `
64779
+ const color = toXlsxHexColor(colors.next());
64780
+ const dataSetNode = escapeXml /*xml*/ `
63815
64781
  <c:ser>
63816
64782
  <c:idx val="${dsIndex}"/>
63817
64783
  <c:order val="${dsIndex}"/>
@@ -63826,8 +64792,9 @@ function addScatterChart(chart) {
63826
64792
  <c:marker>
63827
64793
  <c:symbol val="circle" />
63828
64794
  <c:size val="5"/>
63829
- ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
64795
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63830
64796
  </c:marker>
64797
+ ${extractDataSetLabel(dataset.label)}
63831
64798
  ${chart.labelRange
63832
64799
  ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
63833
64800
  ${numberRef(chart.labelRange)}
@@ -63837,24 +64804,46 @@ function addScatterChart(chart) {
63837
64804
  ${numberRef(dataset.range)}
63838
64805
  </c:yVal>
63839
64806
  </c:ser>
63840
- `);
64807
+ `;
64808
+ if (dataset.rightYAxis) {
64809
+ rightDataSetsNodes.push(dataSetNode);
64810
+ }
64811
+ else {
64812
+ leftDataSetsNodes.push(dataSetNode);
64813
+ }
63841
64814
  }
63842
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63843
64815
  return escapeXml /*xml*/ `
63844
- <c:scatterChart>
63845
- <!-- each data marker in the series does not have a different color -->
63846
- <c:varyColors val="0"/>
63847
- <c:scatterStyle val="lineMarker"/>
63848
- ${joinXmlNodes(dataSetsNodes)}
63849
- <c:axId val="${catAxId}" />
63850
- <c:axId val="${valAxId}" />
63851
- </c:scatterChart>
63852
- ${addAx("b", "c:valAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63853
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
63854
- `;
64816
+ ${leftDataSetsNodes.length
64817
+ ? escapeXml /*xml*/ `
64818
+ <c:scatterChart>
64819
+ <!-- each data marker in the series does not have a different color -->
64820
+ <c:varyColors val="0"/>
64821
+ <c:scatterStyle val="lineMarker"/>
64822
+ ${joinXmlNodes(leftDataSetsNodes)}
64823
+ <c:axId val="${catAxId}" />
64824
+ <c:axId val="${valAxId}" />
64825
+ </c:scatterChart>
64826
+ ${addAx("b", "c:valAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64827
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64828
+ `
64829
+ : ""}
64830
+ ${rightDataSetsNodes.length
64831
+ ? escapeXml /*xml*/ `
64832
+ <c:scatterChart>
64833
+ <!-- each data marker in the series does not have a different color -->
64834
+ <c:varyColors val="0"/>
64835
+ <c:scatterStyle val="lineMarker"/>
64836
+ ${joinXmlNodes(rightDataSetsNodes)}
64837
+ <c:axId val="${catAxId + 1}" />
64838
+ <c:axId val="${valAxId + 1}" />
64839
+ </c:scatterChart>
64840
+ ${addAx("b", "c:valAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64841
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64842
+ `
64843
+ : ""}`;
63855
64844
  }
63856
64845
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
63857
- const colors = new ChartColors();
64846
+ const colors = new ColorGenerator();
63858
64847
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
63859
64848
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
63860
64849
  const dataSetsNodes = [];
@@ -63878,7 +64867,7 @@ function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSiz
63878
64867
  <c:ser>
63879
64868
  <c:idx val="${dsIndex}"/>
63880
64869
  <c:order val="${dsIndex}"/>
63881
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64870
+ ${extractDataSetLabel(dataset.label)}
63882
64871
  ${joinXmlNodes(dataPoints)}
63883
64872
  ${insertDataLabels({ showLeaderLines: true })}
63884
64873
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
@@ -63910,14 +64899,16 @@ function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {
63910
64899
  </dLbls>
63911
64900
  `;
63912
64901
  }
63913
- function addAx(position, axisName, axId, crossAxId, { fontColor }) {
64902
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
63914
64903
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
63915
64904
  // I.e. x-axis, will reference y-axis and vice-versa.
64905
+ const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
63916
64906
  return escapeXml /*xml*/ `
63917
64907
  <${axisName}>
63918
64908
  <c:axId val="${axId}"/>
63919
64909
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
63920
- <c:delete val="0"/> <!-- by default, axis are not displayed -->
64910
+ <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
64911
+ <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
63921
64912
  <c:scaling>
63922
64913
  <c:orientation val="minMax" />
63923
64914
  </c:scaling>
@@ -63927,9 +64918,9 @@ function addAx(position, axisName, axId, crossAxId, { fontColor }) {
63927
64918
  <c:minorTickMark val="none" />
63928
64919
  <c:numFmt formatCode="General" sourceLinked="1" />
63929
64920
  <c:title>
63930
- ${insertText("")}
64921
+ ${insertText(title?.text ?? "", color, 10, title)}
63931
64922
  </c:title>
63932
- ${insertTextProperties(10, fontColor)}
64923
+ ${insertTextProperties(10, defaultFontColor)}
63933
64924
  </${axisName}>
63934
64925
  <!-- <tickLblPos/> omitted -->
63935
64926
  `;
@@ -64810,7 +65801,11 @@ function addRows(construct, data, sheet) {
64810
65801
  let cellNode = escapeXml ``;
64811
65802
  // Either formula or static value inside the cell
64812
65803
  if (cell.isFormula) {
64813
- ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));
65804
+ const res = addFormula(cell);
65805
+ if (!res) {
65806
+ continue;
65807
+ }
65808
+ ({ attrs: additionalAttrs, node: cellNode } = res);
64814
65809
  }
64815
65810
  else if (cell.content && isMarkdownLink(cell.content)) {
64816
65811
  const { label } = parseMarkdownLink(cell.content);
@@ -65816,13 +66811,14 @@ const helpers = {
65816
66811
  UuidGenerator,
65817
66812
  formatValue,
65818
66813
  createCurrencyFormat,
66814
+ ColorGenerator,
65819
66815
  computeTextWidth,
65820
66816
  createEmptyWorkbookData,
65821
66817
  createEmptySheet,
65822
66818
  createEmptyExcelSheet,
65823
66819
  getDefaultChartJsRuntime,
65824
66820
  chartFontColor,
65825
- ChartColors,
66821
+ getChartAxisTitleRuntime,
65826
66822
  getFillingMode,
65827
66823
  rgbaToHex,
65828
66824
  colorToRGBA,
@@ -65879,9 +66875,10 @@ const components = {
65879
66875
  GridOverlay,
65880
66876
  ScorecardChart,
65881
66877
  LineConfigPanel,
65882
- GenericChartDesignPanel,
65883
66878
  BarConfigPanel,
66879
+ PieChartDesignPanel,
65884
66880
  GenericChartConfigPanel,
66881
+ ChartWithAxisDesignPanel,
65885
66882
  GaugeChartConfigPanel,
65886
66883
  GaugeChartDesignPanel,
65887
66884
  ScorecardChartConfigPanel,
@@ -65937,6 +66934,6 @@ const constants = {
65937
66934
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, 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 };
65938
66935
 
65939
66936
 
65940
- __info__.version = "17.3.0-alpha.9";
65941
- __info__.date = "2024-05-24T11:32:11.976Z";
65942
- __info__.hash = "aac246d";
66937
+ __info__.version = "17.3.0";
66938
+ __info__.date = "2024-05-31T14:21:26.547Z";
66939
+ __info__.hash = "271202e";