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

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.1
7
+ * @date 2024-06-03T15:28:03.284Z
8
+ * @hash 605d098
9
9
  */
10
10
 
11
11
  'use strict';
@@ -42,6 +42,8 @@ const COMPOSER_ASSISTANT_COLOR = "#9B359B";
42
42
  const CHART_WATERFALL_POSITIVE_COLOR = "#006FBE";
43
43
  const CHART_WATERFALL_NEGATIVE_COLOR = "#E40000";
44
44
  const CHART_WATERFALL_SUBTOTAL_COLOR = "#AAAAAA";
45
+ const DEFAULT_CHART_PADDING = 20;
46
+ const DEFAULT_CHART_FONT_SIZE = 22;
45
47
  // Color picker defaults as upper case HEX to match `toHex`helper
46
48
  const COLOR_PICKER_DEFAULTS = [
47
49
  "#000000",
@@ -1090,6 +1092,44 @@ function darkenColor(color, percentage) {
1090
1092
  hsla.l = hsla.l - percentage * hsla.l;
1091
1093
  return hslaToHex(hsla);
1092
1094
  }
1095
+ const ColorsList = [
1096
+ // the same colors as those used in odoo reporting
1097
+ "rgb(31,119,180)",
1098
+ "rgb(255,127,14)",
1099
+ "rgb(174,199,232)",
1100
+ "rgb(255,187,120)",
1101
+ "rgb(44,160,44)",
1102
+ "rgb(152,223,138)",
1103
+ "rgb(214,39,40)",
1104
+ "rgb(255,152,150)",
1105
+ "rgb(148,103,189)",
1106
+ "rgb(197,176,213)",
1107
+ "rgb(140,86,75)",
1108
+ "rgb(196,156,148)",
1109
+ "rgb(227,119,194)",
1110
+ "rgb(247,182,210)",
1111
+ "rgb(127,127,127)",
1112
+ "rgb(199,199,199)",
1113
+ "rgb(188,189,34)",
1114
+ "rgb(219,219,141)",
1115
+ "rgb(23,190,207)",
1116
+ "rgb(158,218,229)",
1117
+ ];
1118
+ function getNthColor(index) {
1119
+ return ColorsList[index % ColorsList.length];
1120
+ }
1121
+ class ColorGenerator {
1122
+ currentColorIndex = 0;
1123
+ colors;
1124
+ constructor(colors = []) {
1125
+ this.colors = colors;
1126
+ }
1127
+ next() {
1128
+ return this.colors?.[this.currentColorIndex]
1129
+ ? this.colors[this.currentColorIndex++]
1130
+ : getNthColor(this.currentColorIndex++);
1131
+ }
1132
+ }
1093
1133
 
1094
1134
  //------------------------------------------------------------------------------
1095
1135
  // Coordinate
@@ -1935,11 +1975,13 @@ const invalidateEvaluationCommands = new Set([
1935
1975
  "RENAME_SHEET",
1936
1976
  "DELETE_SHEET",
1937
1977
  "CREATE_SHEET",
1978
+ "DUPLICATE_SHEET",
1938
1979
  "ADD_COLUMNS_ROWS",
1939
1980
  "REMOVE_COLUMNS_ROWS",
1940
1981
  "UNDO",
1941
1982
  "REDO",
1942
1983
  "ADD_MERGE",
1984
+ "REMOVE_MERGE",
1943
1985
  "UPDATE_LOCALE",
1944
1986
  "ADD_PIVOT",
1945
1987
  "UPDATE_PIVOT",
@@ -4878,18 +4920,22 @@ function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
4878
4920
  /**
4879
4921
  * Create a range from a xc. If the xc is empty, this function returns undefined.
4880
4922
  */
4881
- function createRange(getters, sheetId, range) {
4882
- return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
4923
+ function createValidRange(getters, sheetId, xc) {
4924
+ if (!xc)
4925
+ return;
4926
+ const range = getters.getRangeFromSheetXC(sheetId, xc);
4927
+ return !(range.invalidSheetName || range.invalidXc) ? range : undefined;
4883
4928
  }
4884
4929
  /**
4885
4930
  * Spread multiple colrows zone to one row/col zone and add a many new input range as needed.
4886
4931
  * For example, A1:B4 will become [A1:A4, B1:B4]
4887
4932
  */
4888
- function spreadRange(getters, ranges) {
4933
+ function spreadRange(getters, dataSets) {
4889
4934
  const postProcessedRanges = [];
4890
- for (const range of ranges) {
4935
+ for (const dataSet of dataSets) {
4936
+ const range = dataSet.dataRange;
4891
4937
  if (!getters.isRangeValid(range)) {
4892
- postProcessedRanges.push(range); // ignore invalid range
4938
+ postProcessedRanges.push(dataSet); // ignore invalid range
4893
4939
  continue;
4894
4940
  }
4895
4941
  const { sheetName } = splitReference(range);
@@ -4898,27 +4944,33 @@ function spreadRange(getters, ranges) {
4898
4944
  if (zone.bottom !== zone.top && zone.left != zone.right) {
4899
4945
  if (zone.right) {
4900
4946
  for (let j = zone.left; j <= zone.right; ++j) {
4901
- postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
4902
- left: j,
4903
- right: j,
4904
- top: zone.top,
4905
- bottom: zone.bottom,
4906
- })}`);
4947
+ postProcessedRanges.push({
4948
+ ...dataSet,
4949
+ dataRange: `${sheetPrefix}${zoneToXc({
4950
+ left: j,
4951
+ right: j,
4952
+ top: zone.top,
4953
+ bottom: zone.bottom,
4954
+ })}`,
4955
+ });
4907
4956
  }
4908
4957
  }
4909
4958
  else {
4910
4959
  for (let j = zone.top; j <= zone.bottom; ++j) {
4911
- postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
4912
- left: zone.left,
4913
- right: zone.right,
4914
- top: j,
4915
- bottom: j,
4916
- })}`);
4960
+ postProcessedRanges.push({
4961
+ ...dataSet,
4962
+ dataRange: `${sheetPrefix}${zoneToXc({
4963
+ left: zone.left,
4964
+ right: zone.right,
4965
+ top: j,
4966
+ bottom: j,
4967
+ })}`,
4968
+ });
4917
4969
  }
4918
4970
  }
4919
4971
  }
4920
4972
  else {
4921
- postProcessedRanges.push(range);
4973
+ postProcessedRanges.push(dataSet);
4922
4974
  }
4923
4975
  }
4924
4976
  return postProcessedRanges;
@@ -5037,6 +5089,11 @@ function getDefaultCellHeight(ctx, cell, colSize) {
5037
5089
  const fontSize = computeTextFontSizeInPixels(cell.style);
5038
5090
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
5039
5091
  }
5092
+ function getDefaultContextFont(fontSize, bold = false, italic = false) {
5093
+ const italicStr = italic ? "italic" : "";
5094
+ const weight = bold ? "bold" : "";
5095
+ return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
5096
+ }
5040
5097
  const textWidthCache = {};
5041
5098
  function computeTextWidth(context, text, style, fontUnit = "pt") {
5042
5099
  const font = computeTextFont(style, fontUnit);
@@ -5057,6 +5114,28 @@ function computeCachedTextWidth(context, text) {
5057
5114
  }
5058
5115
  return textWidthCache[font][text];
5059
5116
  }
5117
+ const textDimensionsCache = {};
5118
+ function computeTextDimension(context, text, style, fontUnit = "pt") {
5119
+ const font = computeTextFont(style, fontUnit);
5120
+ context.save();
5121
+ context.font = font;
5122
+ const dimensions = computeCachedTextDimension(context, text);
5123
+ context.restore();
5124
+ return dimensions;
5125
+ }
5126
+ function computeCachedTextDimension(context, text) {
5127
+ const font = context.font;
5128
+ if (!textDimensionsCache[font]) {
5129
+ textDimensionsCache[font] = {};
5130
+ }
5131
+ if (textDimensionsCache[font][text] === undefined) {
5132
+ const measure = context.measureText(text);
5133
+ const width = measure.width;
5134
+ const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
5135
+ textDimensionsCache[font][text] = { width, height };
5136
+ }
5137
+ return textDimensionsCache[font][text];
5138
+ }
5060
5139
  function fontSizeInPixels(fontSize) {
5061
5140
  return Math.round((fontSize * 96) / 72);
5062
5141
  }
@@ -8826,7 +8905,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8826
8905
  }
8827
8906
  }
8828
8907
  if (!highlight.noFill) {
8829
- ctx.fillStyle = setColorAlpha(color, highlight.fillAlpha ?? 0.12);
8908
+ ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8830
8909
  ctx.fillRect(x, y, width, height);
8831
8910
  }
8832
8911
  }
@@ -8875,7 +8954,32 @@ class HighlightStore extends SpreadsheetStore {
8875
8954
  }
8876
8955
  }
8877
8956
 
8878
- const NotificationStore = createAbstractStore("Notifications");
8957
+ class NotificationStore {
8958
+ mutators = [
8959
+ "notifyUser",
8960
+ "raiseError",
8961
+ "askConfirmation",
8962
+ "updateNotificationCallbacks",
8963
+ ];
8964
+ notifyUser = (notification) => window.alert(notification.text);
8965
+ askConfirmation = (content, confirm, cancel) => {
8966
+ if (window.confirm(content)) {
8967
+ confirm();
8968
+ }
8969
+ else {
8970
+ cancel?.();
8971
+ }
8972
+ };
8973
+ raiseError = (text, callback) => {
8974
+ window.alert(text);
8975
+ callback?.();
8976
+ };
8977
+ updateNotificationCallbacks(methods) {
8978
+ this.notifyUser = methods.notifyUser || this.notifyUser;
8979
+ this.raiseError = methods.raiseError || this.raiseError;
8980
+ this.askConfirmation = methods.askConfirmation || this.askConfirmation;
8981
+ }
8982
+ }
8879
8983
 
8880
8984
  const CELL_DELETED_MESSAGE = _t("The cell you are trying to edit has been deleted.");
8881
8985
  class ComposerStore extends SpreadsheetStore {
@@ -9896,10 +10000,10 @@ function adaptChartRange(range, applyChange) {
9896
10000
  /**
9897
10001
  * Create the dataSet objects from xcs
9898
10002
  */
9899
- function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
10003
+ function createDataSets(getters, customizedDataSets, sheetId, dataSetsHaveTitle) {
9900
10004
  const dataSets = [];
9901
- for (const sheetXC of dataSetsString) {
9902
- const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
10005
+ for (const dataSet of customizedDataSets) {
10006
+ const dataRange = getters.getRangeFromSheetXC(sheetId, dataSet.dataRange);
9903
10007
  const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
9904
10008
  if (invalidSheetName || invalidXc) {
9905
10009
  continue;
@@ -9916,26 +10020,36 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
9916
10020
  left: column,
9917
10021
  right: column,
9918
10022
  };
9919
- dataSets.push(createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
9920
- ? {
9921
- top: columnZone.top,
9922
- bottom: columnZone.top,
9923
- left: columnZone.left,
9924
- right: columnZone.left,
9925
- }
9926
- : undefined));
10023
+ dataSets.push({
10024
+ ...createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle
10025
+ ? {
10026
+ top: columnZone.top,
10027
+ bottom: columnZone.top,
10028
+ left: columnZone.left,
10029
+ right: columnZone.left,
10030
+ }
10031
+ : undefined),
10032
+ backgroundColor: dataSet.backgroundColor,
10033
+ rightYAxis: dataSet.yAxisId === "y1",
10034
+ customLabel: dataSet.label,
10035
+ });
9927
10036
  }
9928
10037
  }
9929
10038
  else {
9930
10039
  /* 1 cell, 1 row or 1 column */
9931
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
9932
- ? {
9933
- top: zone.top,
9934
- bottom: zone.top,
9935
- left: zone.left,
9936
- right: zone.left,
9937
- }
9938
- : undefined));
10040
+ dataSets.push({
10041
+ ...createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
10042
+ ? {
10043
+ top: zone.top,
10044
+ bottom: zone.top,
10045
+ left: zone.left,
10046
+ right: zone.left,
10047
+ }
10048
+ : undefined),
10049
+ backgroundColor: dataSet.backgroundColor,
10050
+ rightYAxis: dataSet.yAxisId === "y1",
10051
+ customLabel: dataSet.label,
10052
+ });
9939
10053
  }
9940
10054
  }
9941
10055
  return dataSets;
@@ -9975,11 +10089,24 @@ function toExcelDataset(getters, ds) {
9975
10089
  }
9976
10090
  }
9977
10091
  const dataRange = ds.dataRange.clone({ zone: dataZone });
10092
+ let label = {};
10093
+ if (ds.customLabel) {
10094
+ label = {
10095
+ text: ds.customLabel,
10096
+ };
10097
+ }
10098
+ else if (ds.labelCell) {
10099
+ label = {
10100
+ reference: getters.getRangeString(ds.labelCell, "forceSheetReference", {
10101
+ useFixedReference: true,
10102
+ }),
10103
+ };
10104
+ }
9978
10105
  return {
9979
- label: ds.labelCell
9980
- ? getters.getRangeString(ds.labelCell, "forceSheetReference", { useFixedReference: true })
9981
- : undefined,
10106
+ label,
9982
10107
  range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }),
10108
+ backgroundColor: ds.backgroundColor,
10109
+ rightYAxis: ds.rightYAxis,
9983
10110
  };
9984
10111
  }
9985
10112
  function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
@@ -10005,45 +10132,16 @@ function transformChartDefinitionWithDataSetsWithZone(definition, executed) {
10005
10132
  labelRange = labelZone ? zoneToXc(labelZone) : undefined;
10006
10133
  }
10007
10134
  const dataSets = definition.dataSets
10008
- .map(toUnboundedZone)
10135
+ .map((ds) => toUnboundedZone(ds.dataRange))
10009
10136
  .map((zone) => transformZone(zone, executed))
10010
10137
  .filter(isDefined)
10011
- .map(zoneToXc);
10138
+ .map((xc) => ({ dataRange: zoneToXc(xc) }));
10012
10139
  return {
10013
10140
  ...definition,
10014
10141
  labelRange,
10015
10142
  dataSets,
10016
10143
  };
10017
10144
  }
10018
- const GraphColors = [
10019
- // the same colors as those used in odoo reporting
10020
- "rgb(31,119,180)",
10021
- "rgb(255,127,14)",
10022
- "rgb(174,199,232)",
10023
- "rgb(255,187,120)",
10024
- "rgb(44,160,44)",
10025
- "rgb(152,223,138)",
10026
- "rgb(214,39,40)",
10027
- "rgb(255,152,150)",
10028
- "rgb(148,103,189)",
10029
- "rgb(197,176,213)",
10030
- "rgb(140,86,75)",
10031
- "rgb(196,156,148)",
10032
- "rgb(227,119,194)",
10033
- "rgb(247,182,210)",
10034
- "rgb(127,127,127)",
10035
- "rgb(199,199,199)",
10036
- "rgb(188,189,34)",
10037
- "rgb(219,219,141)",
10038
- "rgb(23,190,207)",
10039
- "rgb(158,218,229)",
10040
- ];
10041
- class ChartColors {
10042
- graphColorIndex = 0;
10043
- next() {
10044
- return GraphColors[this.graphColorIndex++ % GraphColors.length];
10045
- }
10046
- }
10047
10145
  /**
10048
10146
  * Choose a font color based on a background color.
10049
10147
  * The font is white with a dark background.
@@ -10056,11 +10154,11 @@ function chartFontColor(backgroundColor) {
10056
10154
  }
10057
10155
  function checkDataset(definition) {
10058
10156
  if (definition.dataSets) {
10059
- const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined;
10157
+ const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range.dataRange)) !== undefined;
10060
10158
  if (invalidRanges) {
10061
10159
  return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
10062
10160
  }
10063
- const zones = definition.dataSets.map(toUnboundedZone);
10161
+ const zones = definition.dataSets.map((ds) => toUnboundedZone(ds.dataRange));
10064
10162
  if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) {
10065
10163
  return "InvalidDataSet" /* CommandResult.InvalidDataSet */;
10066
10164
  }
@@ -10100,6 +10198,35 @@ function getChartPositionAtCenterOfViewport(getters, chartSize) {
10100
10198
  }; // Position at the center of the scrollable viewport
10101
10199
  return position;
10102
10200
  }
10201
+ function getChartAxisTitleRuntime(design) {
10202
+ if (design?.title?.text) {
10203
+ const { text, color, align, italic, bold } = design.title;
10204
+ return {
10205
+ display: true,
10206
+ text,
10207
+ color,
10208
+ font: {
10209
+ style: italic ? "italic" : "normal",
10210
+ weight: bold ? "bold" : "normal",
10211
+ },
10212
+ align: align === "left" ? "start" : align === "right" ? "end" : "center",
10213
+ };
10214
+ }
10215
+ return;
10216
+ }
10217
+ function getDefinedAxis(definition) {
10218
+ let useLeftAxis = false, useRightAxis = false;
10219
+ for (const design of definition.dataSets || []) {
10220
+ if (design.yAxisId === "y1") {
10221
+ useRightAxis = true;
10222
+ }
10223
+ else {
10224
+ useLeftAxis = true;
10225
+ }
10226
+ }
10227
+ useLeftAxis ||= !useRightAxis;
10228
+ return { useLeftAxis, useRightAxis };
10229
+ }
10103
10230
 
10104
10231
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
10105
10232
  if (!baseline) {
@@ -10201,8 +10328,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10201
10328
  type = "scorecard";
10202
10329
  constructor(definition, sheetId, getters) {
10203
10330
  super(definition, sheetId, getters);
10204
- this.keyValue = createRange(getters, sheetId, definition.keyValue);
10205
- this.baseline = createRange(getters, sheetId, definition.baseline);
10331
+ this.keyValue = createValidRange(getters, sheetId, definition.keyValue);
10332
+ this.baseline = createValidRange(getters, sheetId, definition.baseline);
10206
10333
  this.baselineMode = definition.baselineMode;
10207
10334
  this.baselineDescr = definition.baselineDescr;
10208
10335
  this.background = definition.background;
@@ -10217,8 +10344,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10217
10344
  return {
10218
10345
  background: context.background,
10219
10346
  type: "scorecard",
10220
- keyValue: context.range ? context.range[0] : undefined,
10221
- title: context.title || "",
10347
+ keyValue: context.range ? context.range[0].dataRange : undefined,
10348
+ title: context.title || { text: "" },
10222
10349
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
10223
10350
  baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
10224
10351
  baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
@@ -10256,7 +10383,9 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10256
10383
  getContextCreation() {
10257
10384
  return {
10258
10385
  ...this,
10259
- range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,
10386
+ range: this.keyValue
10387
+ ? [{ dataRange: this.getters.getRangeString(this.keyValue, this.sheetId) }]
10388
+ : undefined,
10260
10389
  auxiliaryRange: this.baseline
10261
10390
  ? this.getters.getRangeString(this.baseline, this.sheetId)
10262
10391
  : undefined,
@@ -10303,7 +10432,10 @@ function drawScoreChart(structure, canvas) {
10303
10432
  if (structure.title) {
10304
10433
  ctx.font = structure.title.style.font;
10305
10434
  ctx.fillStyle = structure.title.style.color;
10435
+ const baseline = ctx.textBaseline;
10436
+ ctx.textBaseline = "middle";
10306
10437
  ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
10438
+ ctx.textBaseline = baseline;
10307
10439
  }
10308
10440
  if (structure.baseline) {
10309
10441
  ctx.font = structure.baseline.style.font;
@@ -10390,7 +10522,10 @@ function createScorecardChartRuntime(chart, getters) {
10390
10522
  ? toNumber(baselineDisplay, locale)
10391
10523
  : 0;
10392
10524
  return {
10393
- title: _t(chart.title),
10525
+ title: {
10526
+ ...chart.title,
10527
+ text: _t(chart.title.text ?? ""),
10528
+ },
10394
10529
  keyValue: formattedKeyValue,
10395
10530
  baselineDisplay,
10396
10531
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
@@ -10422,10 +10557,9 @@ function createScorecardChartRuntime(chart, getters) {
10422
10557
  }
10423
10558
 
10424
10559
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
10425
- const TITLE_FONT_SIZE = 18;
10426
10560
  const KEY_BOX_HEIGHT_RATIO = 0.8;
10427
10561
  /* Padding at the border of the chart */
10428
- const CHART_PADDING = 10;
10562
+ const CHART_PADDING = DEFAULT_CHART_PADDING;
10429
10563
  const BOTTOM_PADDING_RATIO = 0.05;
10430
10564
  /**
10431
10565
  * Line height (in em)
@@ -10436,11 +10570,6 @@ function formatBaselineDescr(baselineDescr, baseline) {
10436
10570
  const _baselineDescr = baselineDescr || "";
10437
10571
  return baseline && _baselineDescr ? " " + _baselineDescr : _baselineDescr;
10438
10572
  }
10439
- function getDefaultContextFont(fontSize, bold = false, italic = false) {
10440
- const italicStr = italic ? "italic" : "";
10441
- const weight = bold ? "bold" : "";
10442
- return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
10443
- }
10444
10573
  function getScorecardConfiguration({ width, height }, runtime) {
10445
10574
  const designer = new ScorecardChartConfigBuilder({ width, height }, runtime);
10446
10575
  return designer.computeDesign();
@@ -10468,13 +10597,25 @@ class ScorecardChartConfigBuilder {
10468
10597
  const style = this.getTextStyles();
10469
10598
  let titleHeight = 0;
10470
10599
  if (this.title) {
10471
- ({ height: titleHeight } = this.getFullTextDimensions(this.title, style.title.font));
10600
+ let x, titleWidth;
10601
+ ({ height: titleHeight, width: titleWidth } = this.getFullTextDimensions(this.title, style.title.font));
10602
+ switch (this.runtime.title.align) {
10603
+ case "center":
10604
+ x = (this.width - titleWidth) / 2;
10605
+ break;
10606
+ case "right":
10607
+ x = this.width - titleWidth - CHART_PADDING;
10608
+ break;
10609
+ case "left":
10610
+ default:
10611
+ x = CHART_PADDING;
10612
+ }
10472
10613
  structure.title = {
10473
10614
  text: this.title,
10474
10615
  style: style.title,
10475
10616
  position: {
10476
- x: CHART_PADDING,
10477
- y: CHART_PADDING / 2 + titleHeight,
10617
+ x,
10618
+ y: CHART_PADDING + titleHeight / 2,
10478
10619
  },
10479
10620
  };
10480
10621
  }
@@ -10575,7 +10716,7 @@ class ScorecardChartConfigBuilder {
10575
10716
  return structure;
10576
10717
  }
10577
10718
  get title() {
10578
- return this.runtime.title;
10719
+ return this.runtime.title.text ?? "";
10579
10720
  }
10580
10721
  get keyValue() {
10581
10722
  return this.runtime.keyValue;
@@ -10643,8 +10784,8 @@ class ScorecardChartConfigBuilder {
10643
10784
  }
10644
10785
  return {
10645
10786
  title: {
10646
- font: getDefaultContextFont(TITLE_FONT_SIZE),
10647
- color: this.secondaryFontColor,
10787
+ font: getDefaultContextFont(DEFAULT_CHART_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10788
+ color: this.runtime.title.color ?? this.secondaryFontColor,
10648
10789
  },
10649
10790
  keyValue: {
10650
10791
  color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
@@ -10677,7 +10818,7 @@ class ScorecardChartConfigBuilder {
10677
10818
  getDrawableHeight() {
10678
10819
  const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10679
10820
  let availableHeight = this.height - 2 * verticalPadding;
10680
- availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
10821
+ availableHeight -= this.title ? DEFAULT_CHART_FONT_SIZE * LINE_HEIGHT : 0;
10681
10822
  return availableHeight;
10682
10823
  }
10683
10824
  }
@@ -12027,7 +12168,7 @@ const COUNTUNIQUEIFS = {
12027
12168
  compute: function (range, ...args) {
12028
12169
  let uniqueValues = new Set();
12029
12170
  visitMatchingRanges(args, (i, j) => {
12030
- const data = range[i][j];
12171
+ const data = range[i]?.[j];
12031
12172
  if (isDataNonEmpty(data)) {
12032
12173
  uniqueValues.add(data.value);
12033
12174
  }
@@ -12657,7 +12798,7 @@ const SUMIF = {
12657
12798
  }
12658
12799
  let sum = 0;
12659
12800
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
12660
- const value = sumRange[i][j].value;
12801
+ const value = sumRange[i]?.[j]?.value;
12661
12802
  if (typeof value === "number") {
12662
12803
  sum += value;
12663
12804
  }
@@ -12682,7 +12823,7 @@ const SUMIFS = {
12682
12823
  compute: function (sumRange, ...criters) {
12683
12824
  let sum = 0;
12684
12825
  visitMatchingRanges(criters, (i, j) => {
12685
- const value = sumRange[i][j].value;
12826
+ const value = sumRange[i]?.[j]?.value;
12686
12827
  if (typeof value === "number") {
12687
12828
  sum += value;
12688
12829
  }
@@ -13229,7 +13370,7 @@ const AVERAGEIF = {
13229
13370
  let count = 0;
13230
13371
  let sum = 0;
13231
13372
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
13232
- const value = _averageRange[i][j].value;
13373
+ const value = _averageRange[i]?.[j]?.value;
13233
13374
  if (typeof value === "number") {
13234
13375
  count += 1;
13235
13376
  sum += value;
@@ -13258,7 +13399,7 @@ const AVERAGEIFS = {
13258
13399
  let count = 0;
13259
13400
  let sum = 0;
13260
13401
  visitMatchingRanges(args, (i, j) => {
13261
- const value = _averageRange[i][j].value;
13402
+ const value = _averageRange[i]?.[j]?.value;
13262
13403
  if (typeof value === "number") {
13263
13404
  count += 1;
13264
13405
  sum += value;
@@ -13563,7 +13704,7 @@ const MAXIFS = {
13563
13704
  compute: function (range, ...args) {
13564
13705
  let result = -Infinity;
13565
13706
  visitMatchingRanges(args, (i, j) => {
13566
- const value = range[i][j].value;
13707
+ const value = range[i]?.[j]?.value;
13567
13708
  if (typeof value === "number") {
13568
13709
  result = result < value ? value : result;
13569
13710
  }
@@ -13646,7 +13787,7 @@ const MINIFS = {
13646
13787
  compute: function (range, ...args) {
13647
13788
  let result = Infinity;
13648
13789
  visitMatchingRanges(args, (i, j) => {
13649
- const value = range[i][j].value;
13790
+ const value = range[i]?.[j]?.value;
13650
13791
  if (typeof value === "number") {
13651
13792
  result = result > value ? value : result;
13652
13793
  }
@@ -22103,9 +22244,9 @@ const GAUGE_TEXT_COLOR_HIGH_CONTRAST = "#C8C8C8";
22103
22244
  const GAUGE_INFLECTION_MARKER_COLOR = "#666666aa";
22104
22245
  const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
22105
22246
  const GAUGE_TITLE_SECTION_HEIGHT = 25;
22106
- const GAUGE_TITLE_FONT_SIZE = 18;
22107
- const GAUGE_TITLE_PADDING_LEFT = 10;
22108
- const GAUGE_TITLE_PADDING_TOP = 5;
22247
+ const GAUGE_TITLE_FONT_SIZE = DEFAULT_CHART_FONT_SIZE;
22248
+ const GAUGE_TITLE_PADDING_LEFT = DEFAULT_CHART_PADDING;
22249
+ const GAUGE_TITLE_PADDING_TOP = DEFAULT_CHART_PADDING;
22109
22250
  function drawGaugeChart(canvas, runtime) {
22110
22251
  const canvasBoundingRect = canvas.getBoundingClientRect();
22111
22252
  canvas.width = canvasBoundingRect.width;
@@ -22180,7 +22321,8 @@ function drawInflectionValues(ctx, config) {
22180
22321
  function drawTitle(ctx, config) {
22181
22322
  ctx.save();
22182
22323
  const title = config.title;
22183
- ctx.font = `${title.fontSize}px ${DEFAULT_FONT}`;
22324
+ ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
22325
+ ctx.textBaseline = "middle";
22184
22326
  ctx.fillStyle = title.color;
22185
22327
  ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
22186
22328
  ctx.restore();
@@ -22189,7 +22331,7 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22189
22331
  const maxValue = runtime.maxValue;
22190
22332
  const minValue = runtime.minValue;
22191
22333
  const gaugeValue = runtime.gaugeValue;
22192
- const gaugeRect = getGaugeRect(boundingRect, runtime.title);
22334
+ const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
22193
22335
  const gaugeArcWidth = gaugeRect.width / 6;
22194
22336
  const gaugePercentage = gaugeValue
22195
22337
  ? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
@@ -22219,17 +22361,35 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22219
22361
  };
22220
22362
  const textColor = getContrastedTextColor(runtime.background);
22221
22363
  const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
22364
+ let x = 0, titleWidth = 0, titleHeight = 0;
22365
+ if (runtime.title.text) {
22366
+ ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { ...runtime.title, fontSize: GAUGE_TITLE_FONT_SIZE }, "px"));
22367
+ }
22368
+ switch (runtime.title.align) {
22369
+ case "right":
22370
+ x = boundingRect.width - titleWidth - GAUGE_TITLE_PADDING_LEFT;
22371
+ break;
22372
+ case "center":
22373
+ x = (boundingRect.width - titleWidth) / 2;
22374
+ break;
22375
+ case "left":
22376
+ default:
22377
+ x = GAUGE_TITLE_PADDING_LEFT;
22378
+ break;
22379
+ }
22222
22380
  return {
22223
22381
  width: boundingRect.width,
22224
22382
  height: boundingRect.height,
22225
22383
  title: {
22226
- label: runtime.title,
22384
+ label: runtime.title.text ?? "",
22227
22385
  fontSize: GAUGE_TITLE_FONT_SIZE,
22228
22386
  textPosition: {
22229
- x: GAUGE_TITLE_PADDING_LEFT,
22230
- y: GAUGE_TITLE_PADDING_TOP + GAUGE_TITLE_FONT_SIZE,
22387
+ x,
22388
+ y: GAUGE_TITLE_PADDING_TOP + titleHeight / 2,
22231
22389
  },
22232
- color: textColor,
22390
+ color: runtime.title.color ?? textColor,
22391
+ bold: runtime.title.bold,
22392
+ italic: runtime.title.italic,
22233
22393
  },
22234
22394
  backgroundColor: runtime.background,
22235
22395
  gauge: {
@@ -22519,12 +22679,18 @@ function truncateLabel(label) {
22519
22679
  * Get a default chart js configuration
22520
22680
  */
22521
22681
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22682
+ const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22522
22683
  const options = {
22523
22684
  // https://www.chartjs.org/docs/latest/general/responsive.html
22524
22685
  responsive: true, // will resize when its container is resized
22525
22686
  maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
22526
22687
  layout: {
22527
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
22688
+ padding: {
22689
+ left: DEFAULT_CHART_PADDING,
22690
+ right: DEFAULT_CHART_PADDING,
22691
+ top: chartTitle.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
22692
+ bottom: DEFAULT_CHART_PADDING,
22693
+ },
22528
22694
  },
22529
22695
  elements: {
22530
22696
  line: {
@@ -22537,10 +22703,15 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
22537
22703
  animation: false,
22538
22704
  plugins: {
22539
22705
  title: {
22540
- display: !!chart.title,
22541
- text: _t(chart.title),
22542
- color: fontColor,
22543
- font: { size: 22, weight: "normal" },
22706
+ display: !!chartTitle.text,
22707
+ text: _t(chartTitle.text),
22708
+ color: chartTitle?.color ?? fontColor,
22709
+ align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
22710
+ font: {
22711
+ size: DEFAULT_CHART_FONT_SIZE,
22712
+ weight: chartTitle.bold ? "bold" : "normal",
22713
+ style: chartTitle.italic ? "italic" : "normal",
22714
+ },
22544
22715
  },
22545
22716
  legend: {
22546
22717
  // Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.
@@ -22675,9 +22846,10 @@ function chartToImage(runtime, figure, type) {
22675
22846
  // we have to add the canvas to the DOM otherwise it won't be rendered
22676
22847
  document.body.append(div);
22677
22848
  if ("chartJsConfig" in runtime) {
22678
- runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
22849
+ const config = deepCopy(runtime.chartJsConfig);
22850
+ config.plugins = [backgroundColorChartJSPlugin];
22679
22851
  // @ts-ignore
22680
- const chart = new window.Chart(canvas, runtime.chartJsConfig);
22852
+ const chart = new window.Chart(canvas, config);
22681
22853
  const imgContent = chart.toBase64Image();
22682
22854
  chart.destroy();
22683
22855
  div.remove();
@@ -22718,22 +22890,24 @@ class BarChart extends AbstractChart {
22718
22890
  dataSets;
22719
22891
  labelRange;
22720
22892
  background;
22721
- verticalAxisPosition;
22722
22893
  legendPosition;
22723
22894
  stacked;
22724
22895
  aggregated;
22725
22896
  type = "bar";
22726
22897
  dataSetsHaveTitle;
22898
+ dataSetDesign;
22899
+ axesDesign;
22727
22900
  constructor(definition, sheetId, getters) {
22728
22901
  super(definition, sheetId, getters);
22729
22902
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
22730
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
22903
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
22731
22904
  this.background = definition.background;
22732
- this.verticalAxisPosition = definition.verticalAxisPosition;
22733
22905
  this.legendPosition = definition.legendPosition;
22734
22906
  this.stacked = definition.stacked;
22735
22907
  this.aggregated = definition.aggregated;
22736
22908
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22909
+ this.dataSetDesign = definition.dataSets;
22910
+ this.axesDesign = definition.axesDesign;
22737
22911
  }
22738
22912
  static transformDefinition(definition, executed) {
22739
22913
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22744,21 +22918,28 @@ class BarChart extends AbstractChart {
22744
22918
  static getDefinitionFromContextCreation(context) {
22745
22919
  return {
22746
22920
  background: context.background,
22747
- dataSets: context.range ? context.range : [],
22921
+ dataSets: context.range ?? [],
22748
22922
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22749
22923
  stacked: context.stacked ?? false,
22750
22924
  aggregated: context.aggregated ?? false,
22751
22925
  legendPosition: context.legendPosition ?? "top",
22752
- title: context.title || "",
22926
+ title: context.title || { text: "" },
22753
22927
  type: "bar",
22754
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
22755
22928
  labelRange: context.auxiliaryRange || undefined,
22929
+ axesDesign: context.axesDesign,
22756
22930
  };
22757
22931
  }
22758
22932
  getContextCreation() {
22933
+ const range = [];
22934
+ for (const [i, dataSet] of this.dataSets.entries()) {
22935
+ range.push({
22936
+ ...this.dataSetDesign?.[i],
22937
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
22938
+ });
22939
+ }
22759
22940
  return {
22760
22941
  ...this,
22761
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
22942
+ range,
22762
22943
  auxiliaryRange: this.labelRange
22763
22944
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22764
22945
  : undefined,
@@ -22778,19 +22959,26 @@ class BarChart extends AbstractChart {
22778
22959
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
22779
22960
  }
22780
22961
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
22962
+ const ranges = [];
22963
+ for (const [i, dataSet] of dataSets.entries()) {
22964
+ ranges.push({
22965
+ ...this.dataSetDesign?.[i],
22966
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
22967
+ });
22968
+ }
22781
22969
  return {
22782
22970
  type: "bar",
22783
22971
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
22784
22972
  background: this.background,
22785
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
22973
+ dataSets: ranges,
22786
22974
  legendPosition: this.legendPosition,
22787
- verticalAxisPosition: this.verticalAxisPosition,
22788
22975
  labelRange: labelRange
22789
22976
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
22790
22977
  : undefined,
22791
22978
  title: this.title,
22792
22979
  stacked: this.stacked,
22793
22980
  aggregated: this.aggregated,
22981
+ axesDesign: this.axesDesign,
22794
22982
  };
22795
22983
  }
22796
22984
  getDefinitionForExcel() {
@@ -22801,12 +22989,14 @@ class BarChart extends AbstractChart {
22801
22989
  .map((ds) => toExcelDataset(this.getters, ds))
22802
22990
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22803
22991
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
22992
+ const definition = this.getDefinition();
22804
22993
  return {
22805
- ...this.getDefinition(),
22994
+ ...definition,
22806
22995
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
22807
22996
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
22808
22997
  dataSets,
22809
22998
  labelRange,
22999
+ verticalAxis: getDefinedAxis(definition),
22810
23000
  };
22811
23001
  }
22812
23002
  updateRanges(applyChange) {
@@ -22840,30 +23030,51 @@ function getBarConfiguration(chart, labels, localeFormat) {
22840
23030
  padding: 5,
22841
23031
  color: fontColor,
22842
23032
  },
23033
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
22843
23034
  },
22844
- y: {
22845
- position: chart.verticalAxisPosition,
22846
- beginAtZero: true, // the origin of the y axis is always zero
22847
- ticks: {
22848
- color: fontColor,
22849
- callback: (value) => {
22850
- value = Number(value);
22851
- if (isNaN(value))
22852
- return value;
22853
- const { locale, format } = localeFormat;
22854
- return formatValue(value, {
22855
- locale,
22856
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
22857
- });
22858
- },
23035
+ };
23036
+ const yAxis = {
23037
+ beginAtZero: true, // the origin of the y axis is always zero
23038
+ ticks: {
23039
+ color: fontColor,
23040
+ callback: (value) => {
23041
+ value = Number(value);
23042
+ if (isNaN(value))
23043
+ return value;
23044
+ const { locale, format } = localeFormat;
23045
+ return formatValue(value, {
23046
+ locale,
23047
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23048
+ });
22859
23049
  },
22860
23050
  },
22861
23051
  };
23052
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23053
+ if (useLeftAxis) {
23054
+ config.options.scales.y = {
23055
+ ...yAxis,
23056
+ position: "left",
23057
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23058
+ };
23059
+ }
23060
+ if (useRightAxis) {
23061
+ config.options.scales.y1 = {
23062
+ ...yAxis,
23063
+ position: "right",
23064
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23065
+ };
23066
+ }
22862
23067
  if (chart.stacked) {
22863
23068
  // @ts-ignore chart.js type is broken
22864
23069
  config.options.scales.x.stacked = true;
22865
- // @ts-ignore chart.js type is broken
22866
- config.options.scales.y.stacked = true;
23070
+ if (useLeftAxis) {
23071
+ // @ts-ignore chart.js type is broken
23072
+ config.options.scales.y.stacked = true;
23073
+ }
23074
+ if (useRightAxis) {
23075
+ // @ts-ignore chart.js type is broken
23076
+ config.options.scales.y1.stacked = true;
23077
+ }
22867
23078
  }
22868
23079
  return config;
22869
23080
  }
@@ -22883,8 +23094,9 @@ function createBarChartRuntime(chart, getters) {
22883
23094
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
22884
23095
  const locale = getters.getLocale();
22885
23096
  const config = getBarConfiguration(chart, labels, { format: dataSetFormat, locale });
22886
- const colors = new ChartColors();
22887
- for (let { label, data } of dataSetsValues) {
23097
+ const colors = new ColorGenerator();
23098
+ const definition = chart.getDefinition();
23099
+ for (const { label, data } of dataSetsValues) {
22888
23100
  const color = colors.next();
22889
23101
  const dataset = {
22890
23102
  label,
@@ -22894,29 +23106,43 @@ function createBarChartRuntime(chart, getters) {
22894
23106
  };
22895
23107
  config.data.datasets.push(dataset);
22896
23108
  }
23109
+ for (const [index, dataset] of config.data.datasets.entries()) {
23110
+ if (definition.dataSets?.[index]?.backgroundColor) {
23111
+ const color = definition.dataSets[index].backgroundColor;
23112
+ dataset.backgroundColor = color;
23113
+ dataset.borderColor = color;
23114
+ }
23115
+ if (definition.dataSets?.[index]?.label) {
23116
+ const label = definition.dataSets[index].label;
23117
+ dataset.label = label;
23118
+ }
23119
+ if (definition.dataSets?.[index]?.yAxisId) {
23120
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23121
+ }
23122
+ }
22897
23123
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
22898
23124
  }
22899
23125
 
22900
23126
  class ComboChart extends AbstractChart {
22901
- useBothYAxis;
22902
23127
  dataSets;
22903
23128
  labelRange;
22904
23129
  background;
22905
- verticalAxisPosition;
22906
23130
  legendPosition;
22907
23131
  aggregated;
22908
23132
  dataSetsHaveTitle;
23133
+ dataSetDesign;
23134
+ axesDesign;
22909
23135
  type = "combo";
22910
23136
  constructor(definition, sheetId, getters) {
22911
23137
  super(definition, sheetId, getters);
22912
23138
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
22913
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
23139
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
22914
23140
  this.background = definition.background;
22915
- this.verticalAxisPosition = definition.verticalAxisPosition;
22916
23141
  this.legendPosition = definition.legendPosition;
22917
23142
  this.aggregated = definition.aggregated;
22918
23143
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22919
- this.useBothYAxis = definition.useBothYAxis;
23144
+ this.dataSetDesign = definition.dataSets;
23145
+ this.axesDesign = definition.axesDesign;
22920
23146
  }
22921
23147
  static transformDefinition(definition, executed) {
22922
23148
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22925,9 +23151,16 @@ class ComboChart extends AbstractChart {
22925
23151
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
22926
23152
  }
22927
23153
  getContextCreation() {
23154
+ const range = [];
23155
+ for (const [i, dataSet] of this.dataSets.entries()) {
23156
+ range.push({
23157
+ ...this.dataSetDesign?.[i],
23158
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
23159
+ });
23160
+ }
22928
23161
  return {
22929
23162
  ...this,
22930
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23163
+ range,
22931
23164
  auxiliaryRange: this.labelRange
22932
23165
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22933
23166
  : undefined,
@@ -22937,19 +23170,25 @@ class ComboChart extends AbstractChart {
22937
23170
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
22938
23171
  }
22939
23172
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
23173
+ const ranges = [];
23174
+ for (const [i, dataSet] of dataSets.entries()) {
23175
+ ranges.push({
23176
+ ...this.dataSetDesign?.[i],
23177
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
23178
+ });
23179
+ }
22940
23180
  return {
22941
23181
  type: "combo",
22942
23182
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
22943
23183
  background: this.background,
22944
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
23184
+ dataSets: ranges,
22945
23185
  legendPosition: this.legendPosition,
22946
- verticalAxisPosition: this.verticalAxisPosition,
22947
23186
  labelRange: labelRange
22948
23187
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
22949
23188
  : undefined,
22950
23189
  title: this.title,
22951
23190
  aggregated: this.aggregated,
22952
- useBothYAxis: this.useBothYAxis,
23191
+ axesDesign: this.axesDesign,
22953
23192
  };
22954
23193
  }
22955
23194
  getDefinitionForExcel() {
@@ -22961,12 +23200,14 @@ class ComboChart extends AbstractChart {
22961
23200
  .map((ds) => toExcelDataset(this.getters, ds))
22962
23201
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22963
23202
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
23203
+ const definition = this.getDefinition();
22964
23204
  return {
22965
- ...this.getDefinition(),
23205
+ ...definition,
22966
23206
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
22967
23207
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
22968
23208
  dataSets,
22969
23209
  labelRange,
23210
+ verticalAxis: getDefinedAxis(definition),
22970
23211
  };
22971
23212
  }
22972
23213
  updateRanges(applyChange) {
@@ -22984,11 +23225,10 @@ class ComboChart extends AbstractChart {
22984
23225
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22985
23226
  aggregated: context.aggregated,
22986
23227
  legendPosition: context.legendPosition ?? "top",
22987
- title: context.title || "",
22988
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
23228
+ title: context.title || { text: "" },
22989
23229
  labelRange: context.auxiliaryRange || undefined,
22990
23230
  type: "combo",
22991
- useBothYAxis: false,
23231
+ axesDesign: context.axesDesign,
22992
23232
  };
22993
23233
  }
22994
23234
  copyForSheetId(sheetId) {
@@ -23003,7 +23243,10 @@ class ComboChart extends AbstractChart {
23003
23243
  }
23004
23244
  }
23005
23245
  function createComboChartRuntime(chart, getters) {
23006
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
23246
+ const mainDataSetFormat = chart.dataSets.length
23247
+ ? getChartDatasetFormat(getters, [chart.dataSets[0]])
23248
+ : undefined;
23249
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
23007
23250
  const locale = getters.getLocale();
23008
23251
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23009
23252
  let labels = labelValues.formattedValues;
@@ -23017,11 +23260,12 @@ function createComboChartRuntime(chart, getters) {
23017
23260
  if (chart.aggregated) {
23018
23261
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
23019
23262
  }
23020
- const localeFormat = { format: dataSetFormat, locale };
23263
+ const localeFormat = { format: mainDataSetFormat, locale };
23021
23264
  const fontColor = chartFontColor(chart.background);
23022
23265
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23023
23266
  const legend = {
23024
23267
  labels: { color: fontColor },
23268
+ reverse: true,
23025
23269
  };
23026
23270
  if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
23027
23271
  legend.display = false;
@@ -23039,52 +23283,64 @@ function createComboChartRuntime(chart, getters) {
23039
23283
  padding: 5,
23040
23284
  color: fontColor,
23041
23285
  },
23286
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23042
23287
  },
23043
23288
  };
23044
- const verticalAxis = {
23289
+ const formatCallback = (format) => {
23290
+ return (value) => {
23291
+ value = Number(value);
23292
+ if (isNaN(value))
23293
+ return value;
23294
+ const { locale } = localeFormat;
23295
+ return formatValue(value, {
23296
+ locale,
23297
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23298
+ });
23299
+ };
23300
+ };
23301
+ const leftVerticalAxis = {
23045
23302
  beginAtZero: true, // the origin of the y axis is always zero
23046
23303
  ticks: {
23047
23304
  color: fontColor,
23048
- callback: (value) => {
23049
- value = Number(value);
23050
- if (isNaN(value))
23051
- return value;
23052
- const { locale, format } = localeFormat;
23053
- return formatValue(value, {
23054
- locale,
23055
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23056
- });
23057
- },
23305
+ callback: formatCallback(mainDataSetFormat),
23058
23306
  },
23059
23307
  };
23060
- if (chart.useBothYAxis) {
23308
+ const rightVerticalAxis = {
23309
+ beginAtZero: true, // the origin of the y axis is always zero
23310
+ ticks: {
23311
+ color: fontColor,
23312
+ callback: formatCallback(lineDataSetsFormat),
23313
+ },
23314
+ };
23315
+ const definition = chart.getDefinition();
23316
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23317
+ if (useLeftAxis) {
23061
23318
  config.options.scales.y = {
23062
- ...verticalAxis,
23319
+ ...leftVerticalAxis,
23063
23320
  position: "left",
23321
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23064
23322
  };
23323
+ }
23324
+ if (useRightAxis) {
23065
23325
  config.options.scales.y1 = {
23066
- ...verticalAxis,
23326
+ ...rightVerticalAxis,
23067
23327
  position: "right",
23068
23328
  grid: {
23069
23329
  display: false,
23070
23330
  },
23331
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23071
23332
  };
23072
23333
  }
23073
- else {
23074
- config.options.scales.y = {
23075
- ...verticalAxis,
23076
- position: chart.verticalAxisPosition,
23077
- };
23078
- }
23079
- const colors = new ChartColors();
23334
+ const colors = new ColorGenerator();
23080
23335
  for (let [index, { label, data }] of dataSetsValues.entries()) {
23336
+ const design = definition.dataSets[index];
23081
23337
  const color = colors.next();
23082
23338
  const dataset = {
23083
- label,
23339
+ label: design?.label ?? label,
23084
23340
  data,
23085
- borderColor: color,
23086
- backgroundColor: color,
23087
- yAxisID: index > 0 && chart.useBothYAxis ? "y1" : "y",
23341
+ borderColor: design?.backgroundColor ?? color,
23342
+ backgroundColor: design.backgroundColor ?? color,
23343
+ yAxisID: design?.yAxisId ?? "y",
23088
23344
  type: index === 0 ? "bar" : "line",
23089
23345
  order: -index,
23090
23346
  };
@@ -23165,7 +23421,7 @@ class GaugeChart extends AbstractChart {
23165
23421
  type = "gauge";
23166
23422
  constructor(definition, sheetId, getters) {
23167
23423
  super(definition, sheetId, getters);
23168
- this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);
23424
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
23169
23425
  this.sectionRule = definition.sectionRule;
23170
23426
  this.background = definition.background;
23171
23427
  }
@@ -23185,9 +23441,9 @@ class GaugeChart extends AbstractChart {
23185
23441
  static getDefinitionFromContextCreation(context) {
23186
23442
  return {
23187
23443
  background: context.background,
23188
- title: context.title || "",
23444
+ title: context.title || { text: "" },
23189
23445
  type: "gauge",
23190
- dataRange: context.range ? context.range[0] : undefined,
23446
+ dataRange: context.range ? context.range[0].dataRange : undefined,
23191
23447
  sectionRule: {
23192
23448
  colors: {
23193
23449
  lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
@@ -23238,7 +23494,7 @@ class GaugeChart extends AbstractChart {
23238
23494
  return {
23239
23495
  ...this,
23240
23496
  range: this.dataRange
23241
- ? [this.getters.getRangeString(this.dataRange, this.sheetId)]
23497
+ ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
23242
23498
  : undefined,
23243
23499
  };
23244
23500
  }
@@ -23301,7 +23557,7 @@ function createGaugeChartRuntime(chart, getters) {
23301
23557
  colors.push(chartColors.upperColor);
23302
23558
  return {
23303
23559
  background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
23304
- title: chart.title,
23560
+ title: chart.title ?? { text: "" },
23305
23561
  minValue: {
23306
23562
  value: minValue,
23307
23563
  label: formatValue(minValue, { locale, format }),
@@ -23559,28 +23815,49 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23559
23815
  padding: 5,
23560
23816
  color: fontColor,
23561
23817
  },
23818
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23562
23819
  },
23563
- y: {
23564
- position: chart.verticalAxisPosition,
23565
- beginAtZero: true, // the origin of the y axis is always zero
23566
- ticks: {
23567
- color: fontColor,
23568
- callback: (value) => {
23569
- value = Number(value);
23570
- if (isNaN(value))
23571
- return value;
23572
- const { locale, format } = options;
23573
- return formatValue(value, {
23574
- locale,
23575
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23576
- });
23577
- },
23820
+ };
23821
+ const yAxis = {
23822
+ beginAtZero: true, // the origin of the y axis is always zero
23823
+ ticks: {
23824
+ color: fontColor,
23825
+ callback: (value) => {
23826
+ value = Number(value);
23827
+ if (isNaN(value))
23828
+ return value;
23829
+ const { locale, format } = options;
23830
+ return formatValue(value, {
23831
+ locale,
23832
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23833
+ });
23578
23834
  },
23579
23835
  },
23580
23836
  };
23581
- if ("stacked" in chart && chart.stacked && config.options?.scales?.y) {
23582
- // @ts-ignore chart.js type is wrong
23583
- config.options.scales.y.stacked = true;
23837
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23838
+ if (useLeftAxis) {
23839
+ config.options.scales.y = {
23840
+ ...yAxis,
23841
+ position: "left",
23842
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23843
+ };
23844
+ }
23845
+ if (useRightAxis) {
23846
+ config.options.scales.y1 = {
23847
+ ...yAxis,
23848
+ position: "right",
23849
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23850
+ };
23851
+ }
23852
+ if ("stacked" in chart && chart.stacked) {
23853
+ if (useLeftAxis) {
23854
+ // @ts-ignore chart.js type is broken
23855
+ config.options.scales.y.stacked = true;
23856
+ }
23857
+ if (useRightAxis) {
23858
+ // @ts-ignore chart.js type is broken
23859
+ config.options.scales.y1.stacked = true;
23860
+ }
23584
23861
  }
23585
23862
  return config;
23586
23863
  }
@@ -23627,7 +23904,8 @@ function createLineOrScatterChartRuntime(chart, getters) {
23627
23904
  }
23628
23905
  const stacked = "stacked" in chart ? chart.stacked : false;
23629
23906
  const cumulative = "cumulative" in chart ? chart.cumulative : false;
23630
- const colors = new ChartColors();
23907
+ const colors = new ColorGenerator();
23908
+ const definition = chart.getDefinition();
23631
23909
  for (let [index, { label, data }] of dataSetsValues.entries()) {
23632
23910
  if (["linear", "time"].includes(axisType)) {
23633
23911
  // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
@@ -23660,6 +23938,22 @@ function createLineOrScatterChartRuntime(chart, getters) {
23660
23938
  };
23661
23939
  config.data.datasets.push(dataset);
23662
23940
  }
23941
+ for (const [index, dataset] of config.data.datasets.entries()) {
23942
+ if (definition.dataSets?.[index]?.backgroundColor) {
23943
+ const color = definition.dataSets[index].backgroundColor;
23944
+ dataset.backgroundColor = color;
23945
+ dataset.borderColor = color;
23946
+ //@ts-ignore
23947
+ dataset.pointBackgroundColor = color;
23948
+ }
23949
+ if (definition.dataSets?.[index]?.label) {
23950
+ const label = definition.dataSets[index].label;
23951
+ dataset.label = label;
23952
+ }
23953
+ if (definition.dataSets?.[index]?.yAxisId) {
23954
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23955
+ }
23956
+ }
23663
23957
  return {
23664
23958
  chartJsConfig: config,
23665
23959
  background: chart.background || BACKGROUND_CHART_COLOR,
@@ -23674,7 +23968,6 @@ class LineChart extends AbstractChart {
23674
23968
  dataSets;
23675
23969
  labelRange;
23676
23970
  background;
23677
- verticalAxisPosition;
23678
23971
  legendPosition;
23679
23972
  labelsAsText;
23680
23973
  stacked;
@@ -23682,18 +23975,21 @@ class LineChart extends AbstractChart {
23682
23975
  type = "line";
23683
23976
  dataSetsHaveTitle;
23684
23977
  cumulative;
23978
+ dataSetDesign;
23979
+ axesDesign;
23685
23980
  constructor(definition, sheetId, getters) {
23686
23981
  super(definition, sheetId, getters);
23687
23982
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23688
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
23983
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
23689
23984
  this.background = definition.background;
23690
- this.verticalAxisPosition = definition.verticalAxisPosition;
23691
23985
  this.legendPosition = definition.legendPosition;
23692
23986
  this.labelsAsText = definition.labelsAsText;
23693
23987
  this.stacked = definition.stacked;
23694
23988
  this.aggregated = definition.aggregated;
23695
23989
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
23696
23990
  this.cumulative = definition.cumulative;
23991
+ this.dataSetDesign = definition.dataSets;
23992
+ this.axesDesign = definition.axesDesign;
23697
23993
  }
23698
23994
  static validateChartDefinition(validator, definition) {
23699
23995
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -23704,30 +24000,36 @@ class LineChart extends AbstractChart {
23704
24000
  static getDefinitionFromContextCreation(context) {
23705
24001
  return {
23706
24002
  background: context.background,
23707
- dataSets: context.range ? context.range : [],
24003
+ dataSets: context.range ?? [],
23708
24004
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23709
24005
  labelsAsText: context.labelsAsText ?? false,
23710
24006
  legendPosition: context.legendPosition ?? "top",
23711
- title: context.title || "",
24007
+ title: context.title || { text: "" },
23712
24008
  type: "line",
23713
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
23714
24009
  labelRange: context.auxiliaryRange || undefined,
23715
24010
  stacked: context.stacked ?? false,
23716
24011
  aggregated: context.aggregated ?? false,
23717
24012
  cumulative: context.cumulative ?? false,
24013
+ axesDesign: context.axesDesign,
23718
24014
  };
23719
24015
  }
23720
24016
  getDefinition() {
23721
24017
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
23722
24018
  }
23723
24019
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24020
+ const ranges = [];
24021
+ for (const [i, dataSet] of dataSets.entries()) {
24022
+ ranges.push({
24023
+ ...this.dataSetDesign?.[i],
24024
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24025
+ });
24026
+ }
23724
24027
  return {
23725
24028
  type: "line",
23726
24029
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23727
24030
  background: this.background,
23728
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24031
+ dataSets: ranges,
23729
24032
  legendPosition: this.legendPosition,
23730
- verticalAxisPosition: this.verticalAxisPosition,
23731
24033
  labelRange: labelRange
23732
24034
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
23733
24035
  : undefined,
@@ -23736,12 +24038,20 @@ class LineChart extends AbstractChart {
23736
24038
  stacked: this.stacked,
23737
24039
  aggregated: this.aggregated,
23738
24040
  cumulative: this.cumulative,
24041
+ axesDesign: this.axesDesign,
23739
24042
  };
23740
24043
  }
23741
24044
  getContextCreation() {
24045
+ const range = [];
24046
+ for (const [i, dataSet] of this.dataSets.entries()) {
24047
+ range.push({
24048
+ ...this.dataSetDesign?.[i],
24049
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24050
+ });
24051
+ }
23742
24052
  return {
23743
24053
  ...this,
23744
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24054
+ range,
23745
24055
  auxiliaryRange: this.labelRange
23746
24056
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23747
24057
  : undefined,
@@ -23763,12 +24073,14 @@ class LineChart extends AbstractChart {
23763
24073
  .map((ds) => toExcelDataset(this.getters, ds))
23764
24074
  .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
23765
24075
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24076
+ const definition = this.getDefinition();
23766
24077
  return {
23767
- ...this.getDefinition(),
24078
+ ...definition,
23768
24079
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23769
24080
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
23770
24081
  dataSets,
23771
24082
  labelRange,
24083
+ verticalAxis: getDefinedAxis(definition),
23772
24084
  };
23773
24085
  }
23774
24086
  copyForSheetId(sheetId) {
@@ -23798,7 +24110,7 @@ class PieChart extends AbstractChart {
23798
24110
  constructor(definition, sheetId, getters) {
23799
24111
  super(definition, sheetId, getters);
23800
24112
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23801
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
24113
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
23802
24114
  this.background = definition.background;
23803
24115
  this.legendPosition = definition.legendPosition;
23804
24116
  this.aggregated = definition.aggregated;
@@ -23813,10 +24125,10 @@ class PieChart extends AbstractChart {
23813
24125
  static getDefinitionFromContextCreation(context) {
23814
24126
  return {
23815
24127
  background: context.background,
23816
- dataSets: context.range ? context.range : [],
24128
+ dataSets: context.range ?? [],
23817
24129
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23818
24130
  legendPosition: context.legendPosition ?? "top",
23819
- title: context.title || "",
24131
+ title: context.title || { text: "" },
23820
24132
  type: "pie",
23821
24133
  labelRange: context.auxiliaryRange || undefined,
23822
24134
  aggregated: context.aggregated ?? false,
@@ -23828,7 +24140,9 @@ class PieChart extends AbstractChart {
23828
24140
  getContextCreation() {
23829
24141
  return {
23830
24142
  ...this,
23831
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24143
+ range: this.dataSets.map((ds) => ({
24144
+ dataRange: this.getters.getRangeString(ds.dataRange, this.sheetId),
24145
+ })),
23832
24146
  auxiliaryRange: this.labelRange
23833
24147
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23834
24148
  : undefined,
@@ -23839,7 +24153,9 @@ class PieChart extends AbstractChart {
23839
24153
  type: "pie",
23840
24154
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23841
24155
  background: this.background,
23842
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24156
+ dataSets: dataSets.map((ds) => ({
24157
+ dataRange: this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId),
24158
+ })),
23843
24159
  legendPosition: this.legendPosition,
23844
24160
  labelRange: labelRange
23845
24161
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
@@ -23870,7 +24186,6 @@ class PieChart extends AbstractChart {
23870
24186
  ...this.getDefinition(),
23871
24187
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23872
24188
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
23873
- verticalAxisPosition: "left", //TODO ExcelChartDefinition should be adapted, but can be done later
23874
24189
  dataSets,
23875
24190
  labelRange,
23876
24191
  };
@@ -23971,9 +24286,8 @@ function createPieChartRuntime(chart, getters) {
23971
24286
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
23972
24287
  const locale = getters.getLocale();
23973
24288
  const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
23974
- const colors = new ChartColors();
23975
- for (let { label, data } of dataSetsValues) {
23976
- const backgroundColor = getPieColors(colors, dataSetsValues);
24289
+ const backgroundColor = getPieColors(new ColorGenerator(), dataSetsValues);
24290
+ for (const { label, data } of dataSetsValues) {
23977
24291
  const dataset = {
23978
24292
  label,
23979
24293
  data,
@@ -23989,22 +24303,24 @@ class ScatterChart extends AbstractChart {
23989
24303
  dataSets;
23990
24304
  labelRange;
23991
24305
  background;
23992
- verticalAxisPosition;
23993
24306
  legendPosition;
23994
24307
  labelsAsText;
23995
24308
  aggregated;
23996
24309
  type = "scatter";
23997
24310
  dataSetsHaveTitle;
24311
+ dataSetDesign;
24312
+ axesDesign;
23998
24313
  constructor(definition, sheetId, getters) {
23999
24314
  super(definition, sheetId, getters);
24000
24315
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
24001
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
24316
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
24002
24317
  this.background = definition.background;
24003
- this.verticalAxisPosition = definition.verticalAxisPosition;
24004
24318
  this.legendPosition = definition.legendPosition;
24005
24319
  this.labelsAsText = definition.labelsAsText;
24006
24320
  this.aggregated = definition.aggregated;
24007
24321
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24322
+ this.dataSetDesign = definition.dataSets;
24323
+ this.axesDesign = definition.axesDesign;
24008
24324
  }
24009
24325
  static validateChartDefinition(validator, definition) {
24010
24326
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24015,40 +24331,54 @@ class ScatterChart extends AbstractChart {
24015
24331
  static getDefinitionFromContextCreation(context) {
24016
24332
  return {
24017
24333
  background: context.background,
24018
- dataSets: context.range ? context.range : [],
24334
+ dataSets: context.range ?? [],
24019
24335
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24020
24336
  labelsAsText: context.labelsAsText ?? false,
24021
24337
  legendPosition: context.legendPosition ?? "top",
24022
- title: context.title || "",
24338
+ title: context.title || { text: "" },
24023
24339
  type: "scatter",
24024
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
24025
24340
  labelRange: context.auxiliaryRange || undefined,
24026
24341
  aggregated: context.aggregated ?? false,
24342
+ axesDesign: context.axesDesign,
24027
24343
  };
24028
24344
  }
24029
24345
  getDefinition() {
24030
24346
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24031
24347
  }
24032
24348
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24349
+ const ranges = [];
24350
+ for (const [i, dataSet] of dataSets.entries()) {
24351
+ ranges.push({
24352
+ ...this.dataSetDesign?.[i],
24353
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24354
+ });
24355
+ }
24033
24356
  return {
24034
24357
  type: "scatter",
24035
24358
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24036
24359
  background: this.background,
24037
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24360
+ dataSets: ranges,
24038
24361
  legendPosition: this.legendPosition,
24039
- verticalAxisPosition: this.verticalAxisPosition,
24040
24362
  labelRange: labelRange
24041
24363
  ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24042
24364
  : undefined,
24043
24365
  title: this.title,
24044
24366
  labelsAsText: this.labelsAsText,
24045
24367
  aggregated: this.aggregated,
24368
+ axesDesign: this.axesDesign,
24046
24369
  };
24047
24370
  }
24048
24371
  getContextCreation() {
24372
+ const range = [];
24373
+ for (const [i, dataSet] of this.dataSets.entries()) {
24374
+ range.push({
24375
+ ...this.dataSetDesign?.[i],
24376
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24377
+ });
24378
+ }
24049
24379
  return {
24050
24380
  ...this,
24051
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24381
+ range,
24052
24382
  auxiliaryRange: this.labelRange
24053
24383
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
24054
24384
  : undefined,
@@ -24071,12 +24401,14 @@ class ScatterChart extends AbstractChart {
24071
24401
  .map((ds) => toExcelDataset(this.getters, ds))
24072
24402
  .filter((ds) => ds.range !== "");
24073
24403
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24404
+ const definition = this.getDefinition();
24074
24405
  return {
24075
- ...this.getDefinition(),
24406
+ ...definition,
24076
24407
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
24077
24408
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
24078
24409
  dataSets,
24079
24410
  labelRange,
24411
+ verticalAxis: getDefinedAxis(definition),
24080
24412
  };
24081
24413
  }
24082
24414
  copyForSheetId(sheetId) {
@@ -24096,13 +24428,6 @@ function createScatterChartRuntime(chart, getters) {
24096
24428
  // have less options than the line chart (it only works with linear labels)
24097
24429
  chartJsConfig.type = "line";
24098
24430
  const configOptions = chartJsConfig.options;
24099
- configOptions.elements = {
24100
- point: {
24101
- radius: 3,
24102
- hoverRadius: 3, // chartJS seems bugged, the point starts with the "hoverRadius" value
24103
- hitRadius: 8,
24104
- },
24105
- };
24106
24431
  const locale = getters.getLocale();
24107
24432
  configOptions.plugins.tooltip.callbacks.title = () => "";
24108
24433
  configOptions.plugins.tooltip.callbacks.label = (tooltipItem) => {
@@ -24139,10 +24464,12 @@ class WaterfallChart extends AbstractChart {
24139
24464
  positiveValuesColor;
24140
24465
  negativeValuesColor;
24141
24466
  subTotalValuesColor;
24467
+ dataSetDesign;
24468
+ axesDesign;
24142
24469
  constructor(definition, sheetId, getters) {
24143
24470
  super(definition, sheetId, getters);
24144
24471
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
24145
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
24472
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24146
24473
  this.background = definition.background;
24147
24474
  this.verticalAxisPosition = definition.verticalAxisPosition;
24148
24475
  this.legendPosition = definition.legendPosition;
@@ -24154,6 +24481,8 @@ class WaterfallChart extends AbstractChart {
24154
24481
  this.negativeValuesColor = definition.negativeValuesColor;
24155
24482
  this.subTotalValuesColor = definition.subTotalValuesColor;
24156
24483
  this.firstValueAsSubtotal = definition.firstValueAsSubtotal;
24484
+ this.dataSetDesign = definition.dataSets;
24485
+ this.axesDesign = definition.axesDesign;
24157
24486
  }
24158
24487
  static transformDefinition(definition, executed) {
24159
24488
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24168,19 +24497,27 @@ class WaterfallChart extends AbstractChart {
24168
24497
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24169
24498
  aggregated: context.aggregated ?? false,
24170
24499
  legendPosition: context.legendPosition ?? "top",
24171
- title: context.title || "",
24500
+ title: context.title || { text: "" },
24172
24501
  type: "waterfall",
24173
- verticalAxisPosition: context.verticalAxisPosition ?? "left",
24502
+ verticalAxisPosition: "left",
24174
24503
  labelRange: context.auxiliaryRange || undefined,
24175
24504
  showSubTotals: context.showSubTotals ?? false,
24176
24505
  showConnectorLines: context.showConnectorLines ?? true,
24177
24506
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
24507
+ axesDesign: context.axesDesign,
24178
24508
  };
24179
24509
  }
24180
24510
  getContextCreation() {
24511
+ const range = [];
24512
+ for (const [i, dataSet] of this.dataSets.entries()) {
24513
+ range.push({
24514
+ ...this.dataSetDesign?.[i],
24515
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24516
+ });
24517
+ }
24181
24518
  return {
24182
24519
  ...this,
24183
- range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
24520
+ range,
24184
24521
  auxiliaryRange: this.labelRange
24185
24522
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
24186
24523
  : undefined,
@@ -24200,11 +24537,18 @@ class WaterfallChart extends AbstractChart {
24200
24537
  return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24201
24538
  }
24202
24539
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24540
+ const ranges = [];
24541
+ for (const [i, dataSet] of dataSets.entries()) {
24542
+ ranges.push({
24543
+ ...this.dataSetDesign?.[i],
24544
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24545
+ });
24546
+ }
24203
24547
  return {
24204
24548
  type: "waterfall",
24205
24549
  dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24206
24550
  background: this.background,
24207
- dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
24551
+ dataSets: ranges,
24208
24552
  legendPosition: this.legendPosition,
24209
24553
  verticalAxisPosition: this.verticalAxisPosition,
24210
24554
  labelRange: labelRange
@@ -24218,6 +24562,7 @@ class WaterfallChart extends AbstractChart {
24218
24562
  negativeValuesColor: this.negativeValuesColor,
24219
24563
  subTotalValuesColor: this.subTotalValuesColor,
24220
24564
  firstValueAsSubtotal: this.firstValueAsSubtotal,
24565
+ axesDesign: this.axesDesign,
24221
24566
  };
24222
24567
  }
24223
24568
  getDefinitionForExcel() {
@@ -24277,6 +24622,7 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24277
24622
  grid: {
24278
24623
  display: false,
24279
24624
  },
24625
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
24280
24626
  },
24281
24627
  y: {
24282
24628
  position: chart.verticalAxisPosition,
@@ -24297,6 +24643,7 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24297
24643
  return context.tick.value === 0 ? 2 : 1;
24298
24644
  },
24299
24645
  },
24646
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
24300
24647
  },
24301
24648
  };
24302
24649
  config.options.plugins.tooltip = {
@@ -26356,13 +26703,14 @@ function getSmartChartDefinition(zone, getters) {
26356
26703
  if (!singleColumn) {
26357
26704
  dataSetZone = { ...zone, left: zone.left + 1 };
26358
26705
  }
26359
- const dataSets = [zoneToXc(dataSetZone)];
26706
+ const dataRange = zoneToXc(dataSetZone);
26707
+ const dataSets = [{ dataRange, yAxisId: "y" }];
26360
26708
  const sheetId = getters.getActiveSheetId();
26361
26709
  const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
26362
26710
  if (getZoneArea(zone) === 1 && topLeftCell?.content) {
26363
26711
  return {
26364
26712
  type: "scorecard",
26365
- title: "",
26713
+ title: { text: "" },
26366
26714
  background: topLeftCell.style?.fillColor || undefined,
26367
26715
  keyValue: zoneToXc(zone),
26368
26716
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
@@ -26398,7 +26746,7 @@ function getSmartChartDefinition(zone, getters) {
26398
26746
  const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
26399
26747
  if (canChartParseLabels(labelRange, getters)) {
26400
26748
  return {
26401
- title,
26749
+ title: { text: title },
26402
26750
  dataSets,
26403
26751
  labelsAsText: false,
26404
26752
  stacked: false,
@@ -26407,7 +26755,6 @@ function getSmartChartDefinition(zone, getters) {
26407
26755
  labelRange: labelRangeXc,
26408
26756
  type: "line",
26409
26757
  dataSetsHaveTitle,
26410
- verticalAxisPosition: "left",
26411
26758
  legendPosition: newLegendPos,
26412
26759
  };
26413
26760
  }
@@ -26415,24 +26762,23 @@ function getSmartChartDefinition(zone, getters) {
26415
26762
  if (singleColumn &&
26416
26763
  getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
26417
26764
  return {
26418
- title: "",
26419
- dataSets,
26765
+ title: { text: "" },
26766
+ dataSets: [{ dataRange }],
26420
26767
  aggregated: true,
26421
- labelRange: dataSets[0],
26768
+ labelRange: dataRange,
26422
26769
  type: "pie",
26423
26770
  legendPosition: "top",
26424
26771
  dataSetsHaveTitle: false,
26425
26772
  };
26426
26773
  }
26427
26774
  return {
26428
- title,
26775
+ title: { text: title },
26429
26776
  dataSets,
26430
26777
  labelRange: labelRangeXc,
26431
26778
  type: "bar",
26432
26779
  stacked: false,
26433
26780
  aggregated: false,
26434
26781
  dataSetsHaveTitle,
26435
- verticalAxisPosition: "left",
26436
26782
  legendPosition: newLegendPos,
26437
26783
  };
26438
26784
  }
@@ -29473,6 +29819,41 @@ class OTRegistry extends Registry {
29473
29819
  }
29474
29820
  const otRegistry = new OTRegistry();
29475
29821
 
29822
+ css /* scss */ `
29823
+ .o-checkbox {
29824
+ display: flex;
29825
+ justify-items: center;
29826
+ input {
29827
+ margin-right: 5px;
29828
+ }
29829
+ }
29830
+ `;
29831
+ class Checkbox extends owl.Component {
29832
+ static template = "o-spreadsheet.Checkbox";
29833
+ static props = {
29834
+ label: { type: String, optional: true },
29835
+ value: { type: Boolean, optional: true },
29836
+ className: { type: String, optional: true },
29837
+ name: { type: String, optional: true },
29838
+ title: { type: String, optional: true },
29839
+ disabled: { type: Boolean, optional: true },
29840
+ onChange: Function,
29841
+ };
29842
+ static defaultProps = { value: false };
29843
+ onChange(ev) {
29844
+ const value = ev.target.checked;
29845
+ this.props.onChange(value);
29846
+ }
29847
+ }
29848
+
29849
+ class Section extends owl.Component {
29850
+ static template = "o_spreadsheet.Section";
29851
+ static props = {
29852
+ class: { type: String, optional: true },
29853
+ slots: Object,
29854
+ };
29855
+ }
29856
+
29476
29857
  // The name is misleading and can be confused with the DOM focus.
29477
29858
  class FocusStore {
29478
29859
  mutators = ["focus", "unfocus"];
@@ -29497,6 +29878,7 @@ class FocusStore {
29497
29878
  class SelectionInputStore extends SpreadsheetStore {
29498
29879
  initialRanges;
29499
29880
  inputHasSingleRange;
29881
+ colors;
29500
29882
  mutators = [
29501
29883
  "resetWithRanges",
29502
29884
  "focusById",
@@ -29512,10 +29894,11 @@ class SelectionInputStore extends SpreadsheetStore {
29512
29894
  inputSheetId;
29513
29895
  focusStore = this.get(FocusStore);
29514
29896
  highlightStore = this.get(HighlightStore);
29515
- constructor(get, initialRanges = [], inputHasSingleRange = false) {
29897
+ constructor(get, initialRanges = [], inputHasSingleRange = false, colors = []) {
29516
29898
  super(get);
29517
29899
  this.initialRanges = initialRanges;
29518
29900
  this.inputHasSingleRange = inputHasSingleRange;
29901
+ this.colors = colors;
29519
29902
  if (inputHasSingleRange && initialRanges.length > 1) {
29520
29903
  throw new Error("Input with a single range cannot be instantiated with several range references.");
29521
29904
  }
@@ -29657,11 +30040,12 @@ class SelectionInputStore extends SpreadsheetStore {
29657
30040
  * e.g. ["A1", "Sheet2!B3", "E12"]
29658
30041
  */
29659
30042
  get selectionInputs() {
30043
+ const generator = new ColorGenerator(this.colors);
29660
30044
  return this.ranges.map((input, index) => Object.assign({}, input, {
29661
30045
  color: this.hasMainFocus &&
29662
30046
  this.focusedRangeIndex !== null &&
29663
30047
  this.getters.isRangeValid(input.xc)
29664
- ? input.color
30048
+ ? generator.next()
29665
30049
  : null,
29666
30050
  isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
29667
30051
  isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
@@ -29738,10 +30122,14 @@ class SelectionInputStore extends SpreadsheetStore {
29738
30122
  */
29739
30123
  insertNewRange(index, values) {
29740
30124
  const currentMaxId = Math.max(0, ...this.ranges.map((range) => Number(range.id)));
30125
+ const colors = new ColorGenerator(this.colors);
30126
+ for (let i = 0; i < index; i++) {
30127
+ colors.next();
30128
+ }
29741
30129
  this.ranges.splice(index, 0, ...values.map((xc, i) => ({
29742
30130
  xc,
29743
30131
  id: currentMaxId + i + 1,
29744
- color: colors$1[(currentMaxId + i) % colors$1.length],
30132
+ color: colors.next(),
29745
30133
  })));
29746
30134
  }
29747
30135
  /**
@@ -29885,6 +30273,7 @@ class SelectionInput extends owl.Component {
29885
30273
  class: { type: String, optional: true },
29886
30274
  onSelectionChanged: { type: Function, optional: true },
29887
30275
  onSelectionConfirmed: { type: Function, optional: true },
30276
+ colors: { type: Array, optional: true, default: [] },
29888
30277
  };
29889
30278
  state = owl.useState({
29890
30279
  isMissing: false,
@@ -29909,7 +30298,7 @@ class SelectionInput extends owl.Component {
29909
30298
  }
29910
30299
  setup() {
29911
30300
  owl.useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
29912
- this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false);
30301
+ this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false, this.props.colors);
29913
30302
  owl.onWillUpdateProps((nextProps) => {
29914
30303
  if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
29915
30304
  this.triggerChange();
@@ -29984,6 +30373,26 @@ class SelectionInput extends owl.Component {
29984
30373
  }
29985
30374
  }
29986
30375
 
30376
+ class ChartDataSeries extends owl.Component {
30377
+ static template = "o-spreadsheet.ChartDataSeries";
30378
+ static components = { SelectionInput, Section };
30379
+ static props = {
30380
+ ranges: Array,
30381
+ hasSingleRange: { type: Boolean, optional: true },
30382
+ onSelectionChanged: Function,
30383
+ onSelectionConfirmed: Function,
30384
+ };
30385
+ get ranges() {
30386
+ return this.props.ranges.map((r) => r.dataRange);
30387
+ }
30388
+ get colors() {
30389
+ return this.props.ranges.map((r) => r.backgroundColor);
30390
+ }
30391
+ get title() {
30392
+ return this.props.hasSingleRange ? _t("Data range") : _t("Data series");
30393
+ }
30394
+ }
30395
+
29987
30396
  css /* scss */ `
29988
30397
  .o-validation-error,
29989
30398
  .o-validation-warning {
@@ -30010,55 +30419,6 @@ class ValidationMessages extends owl.Component {
30010
30419
  }
30011
30420
  }
30012
30421
 
30013
- css /* scss */ `
30014
- .o-checkbox {
30015
- display: flex;
30016
- justify-items: center;
30017
- input {
30018
- margin-right: 5px;
30019
- }
30020
- }
30021
- `;
30022
- class Checkbox extends owl.Component {
30023
- static template = "o-spreadsheet.Checkbox";
30024
- static props = {
30025
- label: { type: String, optional: true },
30026
- value: { type: Boolean, optional: true },
30027
- className: { type: String, optional: true },
30028
- name: { type: String, optional: true },
30029
- title: { type: String, optional: true },
30030
- disabled: { type: Boolean, optional: true },
30031
- onChange: Function,
30032
- };
30033
- static defaultProps = { value: false };
30034
- onChange(ev) {
30035
- const value = ev.target.checked;
30036
- this.props.onChange(value);
30037
- }
30038
- }
30039
-
30040
- class Section extends owl.Component {
30041
- static template = "o_spreadsheet.Section";
30042
- static props = {
30043
- class: { type: String, optional: true },
30044
- slots: Object,
30045
- };
30046
- }
30047
-
30048
- class ChartDataSeries extends owl.Component {
30049
- static template = "o-spreadsheet.ChartDataSeries";
30050
- static components = { SelectionInput, Section };
30051
- static props = {
30052
- ranges: Array,
30053
- hasSingleRange: { type: Boolean, optional: true },
30054
- onSelectionChanged: Function,
30055
- onSelectionConfirmed: Function,
30056
- };
30057
- get title() {
30058
- return this.props.hasSingleRange ? _t("Data range") : _t("Data series");
30059
- }
30060
- }
30061
-
30062
30422
  class ChartErrorSection extends owl.Component {
30063
30423
  static template = "o-spreadsheet.ChartErrorSection";
30064
30424
  static components = { Section, ValidationMessages };
@@ -30087,8 +30447,6 @@ class ChartLabelRange extends owl.Component {
30087
30447
  class GenericChartConfigPanel extends owl.Component {
30088
30448
  static template = "o-spreadsheet-GenericChartConfigPanel";
30089
30449
  static components = {
30090
- SelectionInput,
30091
- ValidationMessages,
30092
30450
  ChartDataSeries,
30093
30451
  ChartLabelRange,
30094
30452
  Section,
@@ -30147,7 +30505,10 @@ class GenericChartConfigPanel extends owl.Component {
30147
30505
  * button "confirm" is clicked
30148
30506
  */
30149
30507
  onDataSeriesRangesChanged(ranges) {
30150
- this.dataSeriesRanges = ranges;
30508
+ this.dataSeriesRanges = ranges.map((dataRange, i) => ({
30509
+ ...this.dataSeriesRanges?.[i],
30510
+ dataRange,
30511
+ }));
30151
30512
  this.state.datasetDispatchResult = this.props.canUpdateChart(this.props.figureId, {
30152
30513
  dataSets: this.dataSeriesRanges,
30153
30514
  });
@@ -30190,7 +30551,7 @@ class GenericChartConfigPanel extends owl.Component {
30190
30551
  }
30191
30552
  const getters = this.env.model.getters;
30192
30553
  const sheetId = getters.getActiveSheetId();
30193
- const labelRange = createRange(getters, sheetId, this.labelRange);
30554
+ const labelRange = createValidRange(getters, sheetId, this.labelRange);
30194
30555
  const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
30195
30556
  if (dataSets.length) {
30196
30557
  return dataSets[0].dataRange.zone.top + 1;
@@ -30219,15 +30580,102 @@ class BarConfigPanel extends GenericChartConfigPanel {
30219
30580
  }
30220
30581
  }
30221
30582
 
30583
+ const ANGLE_DOWN = /*xml*/ `
30584
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 224 256">
30585
+ <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)"/>
30586
+ </svg>
30587
+ `;
30588
+ const BACKGROUND_COLOR = "#fdfdfd";
30589
+ const BORDER_COLOR = "#8b8b8b";
30590
+ css /* scss */ `
30591
+ .o_side_panel_collapsible_title {
30592
+ font-size: 16px;
30593
+ font-weight: bold;
30594
+ cursor: pointer;
30595
+ padding: 6px 0px 6px 6px !important;
30596
+
30597
+ .collapsor:before {
30598
+ transform: rotate(-90deg);
30599
+ content: url("data:image/svg+xml,${encodeURIComponent(ANGLE_DOWN)}");
30600
+ width: 12px;
30601
+ display: inline-block;
30602
+ margin: 0 5px 0px 2px;
30603
+ height: 22px;
30604
+ transform-origin: 7px 10px;
30605
+ transition: transform 0.2s ease-in-out;
30606
+ }
30607
+ .collapsor:not(.collapsed):before {
30608
+ transform: rotate(0);
30609
+ }
30610
+
30611
+ .collapsor:not(.collapsed) {
30612
+ background-color: ${BACKGROUND_COLOR};
30613
+ border: solid ${BORDER_COLOR} 1px;
30614
+ margin: -3px 1px -6px -5px;
30615
+ border-radius: 5px 5px 0px 0px;
30616
+ border-bottom: 0px;
30617
+ transition-delay: 0s;
30618
+ }
30619
+
30620
+ .collapsor {
30621
+ width: 100%;
30622
+ margin: -2px 2px -5px -4px;
30623
+ padding: 2px 0 6px 4px;
30624
+ background-color: transparent;
30625
+ border: solid ${BORDER_COLOR} 0px;
30626
+ transition-delay: 0.35s;
30627
+ transition-property: all;
30628
+ }
30629
+
30630
+ .collapsor.collapsed {
30631
+ }
30632
+ }
30633
+
30634
+ .collapsible_section {
30635
+ background-color: #fff;
30636
+ border: solid ${BORDER_COLOR} 1px;
30637
+ border-top: 0;
30638
+ border-radius: 0 0 5px 5px;
30639
+ margin: 0px 1px 0px 1px;
30640
+
30641
+ &.collapsing,
30642
+ &.show {
30643
+ background-color: ${BACKGROUND_COLOR};
30644
+ }
30645
+
30646
+ &.collapsing {
30647
+ transition: height 0.35s, background-color 0.35s !important;
30648
+ }
30649
+ }
30650
+ `;
30651
+ let CURRENT_COLLAPSIBLE_ID = 0;
30652
+ class SidePanelCollapsible extends owl.Component {
30653
+ static template = "o-spreadsheet-SidePanelCollapsible";
30654
+ static props = {
30655
+ slots: Object,
30656
+ collapsedAtInit: { type: Boolean, optional: true },
30657
+ class: { type: String, optional: true },
30658
+ };
30659
+ currentId = (CURRENT_COLLAPSIBLE_ID++).toString();
30660
+ }
30661
+
30662
+ /**
30663
+ * Start listening to pointer events and apply the given callbacks.
30664
+ *
30665
+ * @returns A function to remove the listeners.
30666
+ */
30222
30667
  function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
30223
- const _onMouseUp = (ev) => {
30224
- onMouseUp(ev);
30668
+ const removeListeners = () => {
30225
30669
  window.removeEventListener("pointerdown", onMouseDown);
30226
30670
  window.removeEventListener("pointerup", _onMouseUp);
30227
30671
  window.removeEventListener("dragstart", _onDragStart);
30228
30672
  window.removeEventListener("pointermove", onMouseMove);
30229
30673
  window.removeEventListener("wheel", onMouseMove);
30230
30674
  };
30675
+ const _onMouseUp = (ev) => {
30676
+ onMouseUp(ev);
30677
+ removeListeners();
30678
+ };
30231
30679
  function _onDragStart(ev) {
30232
30680
  ev.preventDefault();
30233
30681
  }
@@ -30239,6 +30687,7 @@ function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
30239
30687
  // preventDefault() is not allowed in passive event handler.
30240
30688
  // https://chromestatus.com/feature/6662647093133312
30241
30689
  window.addEventListener("wheel", onMouseMove, { passive: false });
30690
+ return removeListeners;
30242
30691
  }
30243
30692
  /**
30244
30693
  * Function to be used during a pointerdown event, this function allows to
@@ -30800,63 +31249,325 @@ class RoundColorPicker extends owl.Component {
30800
31249
  }
30801
31250
  }
30802
31251
 
31252
+ css /* scss */ `
31253
+ .o-chart-title-designer {
31254
+ > span {
31255
+ height: 30px;
31256
+ }
31257
+
31258
+ .o-menu-item-button.active {
31259
+ background-color: #e6f4ea;
31260
+ color: #188038;
31261
+ }
31262
+
31263
+ .o-dropdown-content {
31264
+ overflow-y: auto;
31265
+ overflow-x: hidden;
31266
+ padding: 2px;
31267
+ z-index: 100;
31268
+ box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
31269
+
31270
+ .o-dropdown-line {
31271
+ > span {
31272
+ padding: 4px;
31273
+ }
31274
+ }
31275
+ }
31276
+ }
31277
+ `;
30803
31278
  class ChartTitle extends owl.Component {
30804
31279
  static template = "o-spreadsheet.ChartTitle";
30805
- static components = { Section };
30806
- static props = { title: String, update: Function };
31280
+ static components = { Section, ColorPickerWidget };
31281
+ static props = {
31282
+ title: String,
31283
+ updateTitle: Function,
31284
+ name: { type: String, optional: true },
31285
+ toggleItalic: { type: Function, optional: true },
31286
+ toggleBold: { type: Function, optional: true },
31287
+ updateAlignment: { type: Function, optional: true },
31288
+ updateColor: { type: Function, optional: true },
31289
+ style: { type: Object, optional: true },
31290
+ };
31291
+ openedEl = null;
31292
+ setup() {
31293
+ owl.useExternalListener(window, "click", this.onExternalClick);
31294
+ }
31295
+ state = owl.useState({
31296
+ activeTool: "",
31297
+ });
30807
31298
  updateTitle(ev) {
30808
- this.props.update(ev.target.value);
31299
+ this.props.updateTitle(ev.target.value);
31300
+ }
31301
+ toggleDropdownTool(tool, ev) {
31302
+ const isOpen = this.state.activeTool === tool;
31303
+ this.closeMenus();
31304
+ this.state.activeTool = isOpen ? "" : tool;
31305
+ this.openedEl = isOpen ? null : ev.target;
31306
+ }
31307
+ /**
31308
+ * TODO: This is clearly not a goot way to handle external click, but
31309
+ * we currently have no other way to do it ... Should be done in
31310
+ * another task to handle the fact we want only one menu opened at a
31311
+ * time with something like a menuStore ?
31312
+ */
31313
+ onExternalClick(ev) {
31314
+ if (this.openedEl === ev.target) {
31315
+ return;
31316
+ }
31317
+ this.closeMenus();
31318
+ }
31319
+ onColorPicked(color) {
31320
+ this.props.updateColor?.(color);
31321
+ this.closeMenus();
31322
+ }
31323
+ updateAlignment(aligment) {
31324
+ this.props.updateAlignment?.(aligment);
31325
+ this.closeMenus();
31326
+ }
31327
+ closeMenus() {
31328
+ this.state.activeTool = "";
31329
+ this.openedEl = null;
30809
31330
  }
30810
31331
  }
30811
31332
 
30812
- class GenericChartDesignPanel extends owl.Component {
30813
- static template = "o-spreadsheet-GenericChartDesignPanel";
30814
- static components = { RoundColorPicker, ChartTitle, Section };
31333
+ class AxisDesignEditor extends owl.Component {
31334
+ static template = "o-spreadsheet-AxisDesignEditor";
31335
+ static components = {
31336
+ Section,
31337
+ ChartTitle,
31338
+ };
31339
+ state = owl.useState({ currentAxis: "x" });
31340
+ get axisTitleStyle() {
31341
+ const axisDesign = this.props.definition.axesDesign?.[this.state.currentAxis] ?? {};
31342
+ return {
31343
+ color: "",
31344
+ align: "center",
31345
+ ...axisDesign.title,
31346
+ };
31347
+ }
31348
+ updateAxisTitleColor(color) {
31349
+ const axesDesign = this.props.definition.axesDesign ?? {};
31350
+ axesDesign[this.state.currentAxis] = {
31351
+ ...axesDesign[this.state.currentAxis],
31352
+ title: {
31353
+ ...(axesDesign[this.state.currentAxis]?.title ?? {}),
31354
+ color,
31355
+ },
31356
+ };
31357
+ this.props.updateChart(this.props.figureId, { axesDesign });
31358
+ }
31359
+ toggleBoldAxisTitle() {
31360
+ const axesDesign = this.props.definition.axesDesign ?? {};
31361
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31362
+ axesDesign[this.state.currentAxis] = {
31363
+ ...axesDesign[this.state.currentAxis],
31364
+ title: {
31365
+ ...title,
31366
+ bold: !title?.bold,
31367
+ },
31368
+ };
31369
+ this.props.updateChart(this.props.figureId, { axesDesign });
31370
+ }
31371
+ toggleItalicAxisTitle() {
31372
+ const axesDesign = this.props.definition.axesDesign ?? {};
31373
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31374
+ axesDesign[this.state.currentAxis] = {
31375
+ ...axesDesign[this.state.currentAxis],
31376
+ title: {
31377
+ ...title,
31378
+ italic: !title?.italic,
31379
+ },
31380
+ };
31381
+ this.props.updateChart(this.props.figureId, { axesDesign });
31382
+ }
31383
+ updateAxisTitleAlignment(align) {
31384
+ const axesDesign = this.props.definition.axesDesign ?? {};
31385
+ const title = axesDesign[this.state.currentAxis]?.title ?? {};
31386
+ axesDesign[this.state.currentAxis] = {
31387
+ ...axesDesign[this.state.currentAxis],
31388
+ title: {
31389
+ ...title,
31390
+ align,
31391
+ },
31392
+ };
31393
+ this.props.updateChart(this.props.figureId, { axesDesign });
31394
+ }
31395
+ updateAxisEditor(ev) {
31396
+ const axis = ev.target.value;
31397
+ this.state.currentAxis = axis;
31398
+ }
31399
+ getAxisTitle() {
31400
+ const axesDesign = this.props.definition.axesDesign ?? {};
31401
+ return axesDesign[this.state.currentAxis]?.title.text || "";
31402
+ }
31403
+ updateAxisTitle(text) {
31404
+ const axesDesign = this.props.definition.axesDesign ?? {};
31405
+ axesDesign[this.state.currentAxis] = {
31406
+ ...axesDesign[this.state.currentAxis],
31407
+ title: {
31408
+ ...axesDesign?.[this.state.currentAxis]?.title,
31409
+ text,
31410
+ },
31411
+ };
31412
+ this.props.updateChart(this.props.figureId, { axesDesign });
31413
+ }
31414
+ }
31415
+
31416
+ class GeneralDesignEditor extends owl.Component {
31417
+ static template = "o-spreadsheet-GeneralDesignEditor";
31418
+ static components = {
31419
+ RoundColorPicker,
31420
+ ChartTitle,
31421
+ Section,
31422
+ SidePanelCollapsible,
31423
+ };
30815
31424
  static props = {
30816
31425
  figureId: String,
30817
31426
  definition: Object,
30818
31427
  updateChart: Function,
30819
- canUpdateChart: Function,
31428
+ slots: { type: Object, optional: true },
30820
31429
  };
31430
+ state;
31431
+ setup() {
31432
+ this.state = owl.useState({
31433
+ activeTool: "",
31434
+ });
31435
+ }
30821
31436
  get title() {
30822
- return _t(this.props.definition.title);
31437
+ return this.props.definition.title;
31438
+ }
31439
+ toggleDropdownTool(tool, ev) {
31440
+ const isOpen = this.state.activeTool === tool;
31441
+ this.state.activeTool = isOpen ? "" : tool;
30823
31442
  }
30824
31443
  updateBackgroundColor(color) {
30825
31444
  this.props.updateChart(this.props.figureId, {
30826
31445
  background: color,
30827
31446
  });
30828
31447
  }
30829
- updateTitle(title) {
31448
+ updateTitle(newTitle) {
31449
+ const title = { ...this.title, text: newTitle };
30830
31450
  this.props.updateChart(this.props.figureId, { title });
30831
31451
  }
30832
- updateSelect(attr, ev) {
30833
- this.props.updateChart(this.props.figureId, {
30834
- [attr]: ev.target.value,
30835
- });
31452
+ get titleStyle() {
31453
+ return {
31454
+ align: "left",
31455
+ ...this.title,
31456
+ };
30836
31457
  }
30837
- get backgroundColorTitle() {
30838
- return ChartTerms.BackgroundColor;
31458
+ updateChartTitleColor(color) {
31459
+ const title = { ...this.title, color };
31460
+ this.props.updateChart(this.props.figureId, { title });
31461
+ this.state.activeTool = "";
31462
+ }
31463
+ toggleBoldChartTitle() {
31464
+ let title = this.title;
31465
+ title = { ...title, bold: !title.bold };
31466
+ this.props.updateChart(this.props.figureId, { title });
31467
+ }
31468
+ toggleItalicChartTitle() {
31469
+ let title = this.title;
31470
+ title = { ...title, italic: !title.italic };
31471
+ this.props.updateChart(this.props.figureId, { title });
31472
+ }
31473
+ updateChartTitleAlignment(align) {
31474
+ const title = { ...this.title, align };
31475
+ this.props.updateChart(this.props.figureId, { title });
31476
+ this.state.activeTool = "";
30839
31477
  }
30840
31478
  }
30841
31479
 
30842
- class BarChartDesignPanel extends GenericChartDesignPanel {
30843
- static template = "o-spreadsheet-BarChartDesignPanel";
30844
- }
30845
-
30846
- class ComboChartConfigPanel extends GenericChartConfigPanel {
30847
- static template = "o-spreadsheet-ComboChartConfigPanel";
30848
- get shouldUseRightAxis() {
30849
- return _t("Use right axis for line series");
31480
+ class ChartWithAxisDesignPanel extends owl.Component {
31481
+ static template = "o-spreadsheet-ChartWithAxisDesignPanel";
31482
+ static components = {
31483
+ GeneralDesignEditor,
31484
+ SidePanelCollapsible,
31485
+ Section,
31486
+ AxisDesignEditor,
31487
+ RoundColorPicker,
31488
+ };
31489
+ state = owl.useState({ index: 0 });
31490
+ get axesList() {
31491
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
31492
+ let axes = [{ id: "x", name: _t("Horizontal axis") }];
31493
+ if (useLeftAxis) {
31494
+ axes.push({ id: "y", name: _t("Vertical (left) axis") });
31495
+ }
31496
+ if (useRightAxis) {
31497
+ axes.push({ id: "y1", name: _t("Vertical (right) axis") });
31498
+ }
31499
+ return axes;
30850
31500
  }
30851
- onUpdateUseRightAxis(useBothYAxis) {
31501
+ updateLegendPosition(ev) {
30852
31502
  this.props.updateChart(this.props.figureId, {
30853
- useBothYAxis,
31503
+ legendPosition: ev.target.value,
30854
31504
  });
30855
31505
  }
30856
- }
30857
-
30858
- class ComboChartDesignPanel extends GenericChartDesignPanel {
30859
- static template = "o-spreadsheet-ComboChartDesignPanel";
31506
+ getDataSeries() {
31507
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
31508
+ if (!runtime || !("chartJsConfig" in runtime)) {
31509
+ return [];
31510
+ }
31511
+ return runtime.chartJsConfig.data.datasets.map((d) => d.label);
31512
+ }
31513
+ updateSerieEditor(ev) {
31514
+ const chartId = this.props.figureId;
31515
+ const selectedIndex = ev.target.selectedIndex;
31516
+ const runtime = this.env.model.getters.getChartRuntime(chartId);
31517
+ if (!runtime) {
31518
+ return;
31519
+ }
31520
+ this.state.index = selectedIndex;
31521
+ }
31522
+ updateDataSeriesColor(color) {
31523
+ const dataSets = this.props.definition.dataSets;
31524
+ if (!dataSets?.[this.state.index])
31525
+ return;
31526
+ dataSets[this.state.index] = {
31527
+ ...dataSets[this.state.index],
31528
+ backgroundColor: color,
31529
+ };
31530
+ this.props.updateChart(this.props.figureId, { dataSets });
31531
+ }
31532
+ getDataSerieColor() {
31533
+ const dataSets = this.props.definition.dataSets;
31534
+ if (!dataSets?.[this.state.index])
31535
+ return "";
31536
+ const color = dataSets[this.state.index].backgroundColor;
31537
+ return color ? toHex(color) : getNthColor(this.state.index);
31538
+ }
31539
+ updateDataSeriesAxis(ev) {
31540
+ const axis = ev.target.value;
31541
+ const dataSets = this.props.definition.dataSets;
31542
+ if (!dataSets?.[this.state.index])
31543
+ return;
31544
+ dataSets[this.state.index] = {
31545
+ ...dataSets[this.state.index],
31546
+ yAxisId: axis === "left" ? "y" : "y1",
31547
+ };
31548
+ this.props.updateChart(this.props.figureId, { dataSets });
31549
+ }
31550
+ getDataSerieAxis() {
31551
+ const dataSets = this.props.definition.dataSets;
31552
+ if (!dataSets?.[this.state.index])
31553
+ return "left";
31554
+ return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31555
+ }
31556
+ updateDataSeriesLabel(ev) {
31557
+ const label = ev.target.value;
31558
+ const dataSets = this.props.definition.dataSets;
31559
+ if (!dataSets?.[this.state.index])
31560
+ return;
31561
+ dataSets[this.state.index] = {
31562
+ ...dataSets[this.state.index],
31563
+ label,
31564
+ };
31565
+ this.props.updateChart(this.props.figureId, { dataSets });
31566
+ }
31567
+ getDataSerieLabel() {
31568
+ const dataSets = this.props.definition.dataSets;
31569
+ return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
31570
+ }
30860
31571
  }
30861
31572
 
30862
31573
  class GaugeChartConfigPanel extends owl.Component {
@@ -30891,7 +31602,7 @@ class GaugeChartConfigPanel extends owl.Component {
30891
31602
  });
30892
31603
  }
30893
31604
  getDataRange() {
30894
- return this.dataRange || "";
31605
+ return { dataRange: this.dataRange || "" };
30895
31606
  }
30896
31607
  }
30897
31608
 
@@ -30933,24 +31644,24 @@ css /* scss */ `
30933
31644
  class GaugeChartDesignPanel extends owl.Component {
30934
31645
  static template = "o-spreadsheet-GaugeChartDesignPanel";
30935
31646
  static components = {
30936
- ChartErrorSection,
30937
- RoundColorPicker,
30938
- ChartTitle,
31647
+ SidePanelCollapsible,
30939
31648
  Section,
31649
+ RoundColorPicker,
31650
+ GeneralDesignEditor,
31651
+ ChartErrorSection,
30940
31652
  };
30941
31653
  static props = {
30942
31654
  figureId: String,
30943
31655
  definition: Object,
30944
31656
  updateChart: Function,
30945
- canUpdateChart: Function,
31657
+ canUpdateChart: { type: Function, optional: true },
30946
31658
  };
30947
- state = owl.useState({
30948
- openedMenu: undefined,
30949
- sectionRuleDispatchResult: undefined,
30950
- sectionRule: deepCopy(this.props.definition.sectionRule),
30951
- });
30952
- get title() {
30953
- return _t(this.props.definition.title);
31659
+ state;
31660
+ setup() {
31661
+ this.state = owl.useState({
31662
+ sectionRuleDispatchResult: undefined,
31663
+ sectionRule: deepCopy(this.props.definition.sectionRule),
31664
+ });
30954
31665
  }
30955
31666
  get designErrorMessages() {
30956
31667
  const cancelledReasons = [...(this.state.sectionRuleDispatchResult?.reasons || [])];
@@ -30961,8 +31672,8 @@ class GaugeChartDesignPanel extends owl.Component {
30961
31672
  background: color,
30962
31673
  });
30963
31674
  }
30964
- updateTitle(title) {
30965
- this.props.updateChart(this.props.figureId, { title });
31675
+ updateTitle(content) {
31676
+ this.props.updateChart(this.props.figureId, { title: { text: content } });
30966
31677
  }
30967
31678
  isRangeMinInvalid() {
30968
31679
  return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause("EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */) ||
@@ -31057,8 +31768,23 @@ class LineConfigPanel extends GenericChartConfigPanel {
31057
31768
  }
31058
31769
  }
31059
31770
 
31060
- class LineChartDesignPanel extends GenericChartDesignPanel {
31061
- static template = "o-spreadsheet-LineChartDesignPanel";
31771
+ class PieChartDesignPanel extends owl.Component {
31772
+ static template = "o-spreadsheet-PieChartDesignPanel";
31773
+ static components = {
31774
+ GeneralDesignEditor,
31775
+ Section,
31776
+ };
31777
+ static props = {
31778
+ figureId: String,
31779
+ definition: Object,
31780
+ updateChart: Function,
31781
+ canUpdateChart: { type: Function, optional: true },
31782
+ };
31783
+ updateLegendPosition(ev) {
31784
+ this.props.updateChart(this.props.figureId, {
31785
+ legendPosition: ev.target.value,
31786
+ });
31787
+ }
31062
31788
  }
31063
31789
 
31064
31790
  class ScatterConfigPanel extends GenericChartConfigPanel {
@@ -31152,16 +31878,19 @@ class ScorecardChartConfigPanel extends owl.Component {
31152
31878
 
31153
31879
  class ScorecardChartDesignPanel extends owl.Component {
31154
31880
  static template = "o-spreadsheet-ScorecardChartDesignPanel";
31155
- static components = { RoundColorPicker, ChartTitle, Section, Checkbox };
31881
+ static components = {
31882
+ GeneralDesignEditor,
31883
+ RoundColorPicker,
31884
+ SidePanelCollapsible,
31885
+ Section,
31886
+ Checkbox,
31887
+ };
31156
31888
  static props = {
31157
31889
  figureId: String,
31158
31890
  definition: Object,
31159
31891
  updateChart: Function,
31160
- canUpdateChart: Function,
31892
+ canUpdateChart: { type: Function, optional: true },
31161
31893
  };
31162
- get title() {
31163
- return _t(this.props.definition.title);
31164
- }
31165
31894
  get colorsSectionTitle() {
31166
31895
  return this.props.definition.baselineMode === "progress"
31167
31896
  ? _t("Progress bar colors")
@@ -31170,8 +31899,8 @@ class ScorecardChartDesignPanel extends owl.Component {
31170
31899
  get humanizeNumbersLabel() {
31171
31900
  return _t("Humanize numbers");
31172
31901
  }
31173
- updateTitle(title) {
31174
- this.props.updateChart(this.props.figureId, { title });
31902
+ updateTitle(content) {
31903
+ this.props.updateChart(this.props.figureId, { title: { text: content } });
31175
31904
  }
31176
31905
  updateHumanizeNumbers(humanize) {
31177
31906
  this.props.updateChart(this.props.figureId, { humanize });
@@ -31200,14 +31929,22 @@ class ScorecardChartDesignPanel extends owl.Component {
31200
31929
  }
31201
31930
  }
31202
31931
 
31203
- class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31932
+ class WaterfallChartDesignPanel extends owl.Component {
31204
31933
  static template = "o-spreadsheet-WaterfallChartDesignPanel";
31205
- static components = { ...GenericChartDesignPanel.components, Checkbox, RoundColorPicker };
31206
- state = owl.useState({ pickerOpened: false });
31207
- setup() {
31208
- super.setup();
31209
- owl.useExternalListener(window, "click", this.closePicker);
31210
- }
31934
+ static components = {
31935
+ GeneralDesignEditor,
31936
+ Checkbox,
31937
+ SidePanelCollapsible,
31938
+ Section,
31939
+ RoundColorPicker,
31940
+ AxisDesignEditor,
31941
+ };
31942
+ static props = {
31943
+ figureId: String,
31944
+ definition: Object,
31945
+ updateChart: Function,
31946
+ canUpdateChart: { type: Function, optional: true },
31947
+ };
31211
31948
  onUpdateShowSubTotals(showSubTotals) {
31212
31949
  this.props.updateChart(this.props.figureId, { showSubTotals });
31213
31950
  }
@@ -31220,11 +31957,11 @@ class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31220
31957
  updateColor(colorName, color) {
31221
31958
  this.props.updateChart(this.props.figureId, { [colorName]: color });
31222
31959
  }
31223
- closePicker() {
31224
- this.state.pickerOpened = false;
31225
- }
31226
- togglePicker() {
31227
- this.state.pickerOpened = !this.state.pickerOpened;
31960
+ get axesList() {
31961
+ return [
31962
+ { id: "x", name: _t("Horizontal axis") },
31963
+ { id: "y", name: _t("Vertical axis") },
31964
+ ];
31228
31965
  }
31229
31966
  get positiveValuesColor() {
31230
31967
  return (this.props.definition.positiveValuesColor ||
@@ -31238,29 +31975,39 @@ class WaterfallChartDesignPanel extends GenericChartDesignPanel {
31238
31975
  return (this.props.definition.subTotalValuesColor ||
31239
31976
  CHART_WATERFALL_SUBTOTAL_COLOR);
31240
31977
  }
31978
+ updateLegendPosition(ev) {
31979
+ this.props.updateChart(this.props.figureId, {
31980
+ legendPosition: ev.target.value,
31981
+ });
31982
+ }
31983
+ updateVerticalAxisPosition(ev) {
31984
+ this.props.updateChart(this.props.figureId, {
31985
+ verticalAxisPosition: ev.target.value,
31986
+ });
31987
+ }
31241
31988
  }
31242
31989
 
31243
31990
  const chartSidePanelComponentRegistry = new Registry();
31244
31991
  chartSidePanelComponentRegistry
31245
31992
  .add("line", {
31246
31993
  configuration: LineConfigPanel,
31247
- design: LineChartDesignPanel,
31994
+ design: ChartWithAxisDesignPanel,
31248
31995
  })
31249
31996
  .add("scatter", {
31250
31997
  configuration: ScatterConfigPanel,
31251
- design: LineChartDesignPanel,
31998
+ design: ChartWithAxisDesignPanel,
31252
31999
  })
31253
32000
  .add("bar", {
31254
32001
  configuration: BarConfigPanel,
31255
- design: BarChartDesignPanel,
32002
+ design: ChartWithAxisDesignPanel,
31256
32003
  })
31257
32004
  .add("combo", {
31258
- configuration: ComboChartConfigPanel,
31259
- design: ComboChartDesignPanel,
32005
+ configuration: GenericChartConfigPanel,
32006
+ design: ChartWithAxisDesignPanel,
31260
32007
  })
31261
32008
  .add("pie", {
31262
32009
  configuration: GenericChartConfigPanel,
31263
- design: GenericChartDesignPanel,
32010
+ design: PieChartDesignPanel,
31264
32011
  })
31265
32012
  .add("gauge", {
31266
32013
  configuration: GaugeChartConfigPanel,
@@ -31546,6 +32293,7 @@ function useDragAndDropListItems() {
31546
32293
  state.itemsStyle = {};
31547
32294
  document.body.style.cursor = previousCursor;
31548
32295
  args.onCancel?.();
32296
+ cleanUp();
31549
32297
  };
31550
32298
  const onDragEnd = (itemId, indexAtEnd) => {
31551
32299
  state.draggedItemId = undefined;
@@ -31566,7 +32314,8 @@ function useDragAndDropListItems() {
31566
32314
  onDragEnd,
31567
32315
  onCancel: state.cancel,
31568
32316
  });
31569
- startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
32317
+ const stopListening = startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
32318
+ cleanupFns.push(stopListening);
31570
32319
  const onScroll = dndHelper.onScroll.bind(dndHelper);
31571
32320
  args.containerEl.addEventListener("scroll", onScroll);
31572
32321
  cleanupFns.push(() => args.containerEl.removeEventListener("scroll", onScroll));
@@ -31643,7 +32392,7 @@ class DOMDndHelper {
31643
32392
  this.moveDraggedItemToPosition(this.currentMousePosition + this.scrollOffset);
31644
32393
  }
31645
32394
  onMouseMove(ev) {
31646
- if (ev.button !== -1) {
32395
+ if (ev.button > 1) {
31647
32396
  this.onCancel();
31648
32397
  return;
31649
32398
  }
@@ -34386,7 +35135,7 @@ class SpreadsheetPivotTable {
34386
35135
  * This function converts a list of data entry into a spreadsheet pivot table.
34387
35136
  */
34388
35137
  function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
34389
- const columnsTree = dateEntriesToColumnsTree(dataEntries, definition.columns, 0);
35138
+ const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
34390
35139
  computeWidthOfColumnsNodes(columnsTree, definition.measures.length);
34391
35140
  const cols = columnsTreeToColumns(columnsTree, definition);
34392
35141
  const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
@@ -34437,7 +35186,7 @@ function dataEntriesToRows(dataEntries, index, rows, fields, values) {
34437
35186
  /**
34438
35187
  * Create the columns tree from data entries.
34439
35188
  */
34440
- function dateEntriesToColumnsTree(dataEntries, columns, index) {
35189
+ function dataEntriesToColumnsTree(dataEntries, columns, index) {
34441
35190
  if (index >= columns.length) {
34442
35191
  return [];
34443
35192
  }
@@ -34449,7 +35198,7 @@ function dateEntriesToColumnsTree(dataEntries, columns, index) {
34449
35198
  return {
34450
35199
  value,
34451
35200
  field: colName,
34452
- children: dateEntriesToColumnsTree(groups[value] || [], columns, index + 1),
35201
+ children: dataEntriesToColumnsTree(groups[value] || [], columns, index + 1),
34453
35202
  width: 0,
34454
35203
  };
34455
35204
  });
@@ -34956,7 +35705,12 @@ class SpreadsheetPivot {
34956
35705
  if (!field) {
34957
35706
  throw new Error(`Field ${this.fieldKeys[index]} does not exist`);
34958
35707
  }
34959
- entry[field.name] = cell;
35708
+ if (cell.value === "") {
35709
+ entry[field.name] = { value: null, type: CellValueType.empty };
35710
+ }
35711
+ else {
35712
+ entry[field.name] = cell;
35713
+ }
34960
35714
  }
34961
35715
  entry["__count"] = { value: 1, type: CellValueType.number };
34962
35716
  dataEntries.push(entry);
@@ -34999,7 +35753,14 @@ pivotRegistry.add("SPREADSHEET", {
34999
35753
 
35000
35754
  class PivotSidePanelStore extends SpreadsheetStore {
35001
35755
  pivotId;
35002
- mutators = ["applyUpdate", "renamePivot", "update"];
35756
+ mutators = [
35757
+ "reset",
35758
+ "deferUpdates",
35759
+ "applyUpdate",
35760
+ "discardPendingUpdate",
35761
+ "renamePivot",
35762
+ "update",
35763
+ ];
35003
35764
  updatesAreDeferred = true;
35004
35765
  draft = null;
35005
35766
  constructor(get, pivotId) {
@@ -36712,14 +37473,15 @@ class FigureComponent extends owl.Component {
36712
37473
  }
36713
37474
  onKeyDown(ev) {
36714
37475
  const figure = this.props.figure;
36715
- switch (ev.key) {
37476
+ const keyDownShortcut = keyboardEventToShortcutString(ev);
37477
+ switch (keyDownShortcut) {
36716
37478
  case "Delete":
37479
+ case "Backspace":
36717
37480
  this.env.model.dispatch("DELETE_FIGURE", {
36718
37481
  sheetId: this.env.model.getters.getActiveSheetId(),
36719
37482
  id: figure.id,
36720
37483
  });
36721
37484
  this.props.onFigureDeleted();
36722
- ev.stopPropagation();
36723
37485
  ev.preventDefault();
36724
37486
  ev.stopPropagation();
36725
37487
  break;
@@ -36740,7 +37502,22 @@ class FigureComponent extends owl.Component {
36740
37502
  x: figure.x + delta[0],
36741
37503
  y: figure.y + delta[1],
36742
37504
  });
37505
+ ev.preventDefault();
37506
+ ev.stopPropagation();
37507
+ break;
37508
+ case "Ctrl+A":
37509
+ // Maybe in the future we will implement a way to select all figures
37510
+ ev.preventDefault();
36743
37511
  ev.stopPropagation();
37512
+ break;
37513
+ case "Ctrl+Y":
37514
+ case "Ctrl+Z":
37515
+ if (keyDownShortcut === "Ctrl+Y") {
37516
+ this.env.model.dispatch("REQUEST_REDO");
37517
+ }
37518
+ else if (keyDownShortcut === "Ctrl+Z") {
37519
+ this.env.model.dispatch("REQUEST_UNDO");
37520
+ }
36744
37521
  ev.preventDefault();
36745
37522
  ev.stopPropagation();
36746
37523
  break;
@@ -38020,9 +38797,7 @@ class FilterIcon extends owl.Component {
38020
38797
 
38021
38798
  class FilterIconsOverlay extends owl.Component {
38022
38799
  static template = "o-spreadsheet-FilterIconsOverlay";
38023
- static props = {
38024
- onMouseDown: Function,
38025
- };
38800
+ static props = {};
38026
38801
  static components = {
38027
38802
  GridCellIcon,
38028
38803
  FilterIcon,
@@ -38262,6 +39037,7 @@ class GridOverlay extends owl.Component {
38262
39037
  };
38263
39038
  gridOverlay = owl.useRef("gridOverlay");
38264
39039
  gridOverlayRect = useAbsoluteBoundingRect(this.gridOverlay);
39040
+ cellPopovers;
38265
39041
  setup() {
38266
39042
  useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered);
38267
39043
  const resizeObserver = new ResizeObserver(() => {
@@ -38283,6 +39059,7 @@ class GridOverlay extends owl.Component {
38283
39059
  const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
38284
39060
  return scrollY > 0;
38285
39061
  });
39062
+ this.cellPopovers = useStore(CellPopoverStore);
38286
39063
  }
38287
39064
  get gridOverlayEl() {
38288
39065
  if (!this.gridOverlay.el) {
@@ -38296,16 +39073,18 @@ class GridOverlay extends owl.Component {
38296
39073
  get isPaintingFormat() {
38297
39074
  return this.env.model.getters.isPaintingFormat();
38298
39075
  }
38299
- onMouseDown(ev, modifiers) {
39076
+ onMouseDown(ev) {
38300
39077
  if (ev.button > 0) {
38301
39078
  // not main button, probably a context menu
38302
39079
  return;
38303
39080
  }
39081
+ if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
39082
+ this.cellPopovers.close();
39083
+ }
38304
39084
  const [col, row] = this.getCartesianCoordinates(ev);
38305
39085
  this.props.onCellClicked(col, row, {
38306
39086
  expandZone: ev.shiftKey,
38307
39087
  addZone: isCtrlKey(ev),
38308
- closePopover: modifiers?.closePopover ?? true,
38309
39088
  });
38310
39089
  }
38311
39090
  onDoubleClick(ev) {
@@ -40449,9 +41228,6 @@ class Grid extends owl.Component {
40449
41228
  // Zone selection with mouse
40450
41229
  // ---------------------------------------------------------------------------
40451
41230
  onCellClicked(col, row, modifiers) {
40452
- if (modifiers.closePopover && this.cellPopovers.isOpen) {
40453
- this.cellPopovers.close();
40454
- }
40455
41231
  if (this.composerStore.editionMode === "editing") {
40456
41232
  this.composerStore.stopEdition();
40457
41233
  }
@@ -42324,11 +43100,20 @@ function isImageData(data) {
42324
43100
  return "imageSrc" in data;
42325
43101
  }
42326
43102
  function convertChartData(chartData) {
42327
- const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
43103
+ const dataSetsHaveTitle = chartData.dataSets.some((ds) => "reference" in (ds.label ?? {}));
42328
43104
  const labelRange = chartData.labelRange
42329
43105
  ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
42330
43106
  : undefined;
42331
- let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
43107
+ const dataSets = chartData.dataSets.map((data) => {
43108
+ let label = undefined;
43109
+ if (data.label && "text" in data.label) {
43110
+ label = data.label.text;
43111
+ }
43112
+ return {
43113
+ dataRange: convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle),
43114
+ label,
43115
+ };
43116
+ });
42332
43117
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
42333
43118
  if (chartData.type === "pie") {
42334
43119
  dataSets.reverse();
@@ -42337,10 +43122,9 @@ function convertChartData(chartData) {
42337
43122
  dataSets,
42338
43123
  dataSetsHaveTitle,
42339
43124
  labelRange,
42340
- title: chartData.title || "",
43125
+ title: chartData.title ?? { text: "" },
42341
43126
  type: chartData.type,
42342
43127
  background: convertColor({ rgb: chartData.backgroundColor }) || "#FFFFFF",
42343
- verticalAxisPosition: chartData.verticalAxisPosition,
42344
43128
  legendPosition: chartData.legendPosition,
42345
43129
  stacked: chartData.stacked || false,
42346
43130
  aggregated: false,
@@ -43455,25 +44239,20 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43455
44239
  return this.extractComboChart(rootChartElement);
43456
44240
  }
43457
44241
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
43458
- const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
44242
+ const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:chart > c:title a:t" }, (textElement) => {
43459
44243
  return textElement.textContent || "";
43460
44244
  }).join("");
43461
44245
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
43462
44246
  default: "clustered",
43463
44247
  }).asString();
43464
44248
  return {
43465
- title: chartTitle,
44249
+ title: { text: chartTitle },
43466
44250
  type: CHART_TYPE_CONVERSION_MAP[chartType],
43467
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
44251
+ dataSets: this.extractChartDatasets(this.querySelectorAll(rootChartElement, `c:${chartType}`), chartType),
43468
44252
  labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
43469
44253
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43470
44254
  default: "ffffff",
43471
44255
  }).asString(),
43472
- verticalAxisPosition: this.extractChildAttr(rootChartElement, "c:valAx > c:axPos", "val", {
43473
- default: "l",
43474
- }).asString() === "r"
43475
- ? "right"
43476
- : "left",
43477
44256
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
43478
44257
  default: "b",
43479
44258
  }).asString()],
@@ -43491,21 +44270,16 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43491
44270
  default: "clustered",
43492
44271
  }).asString();
43493
44272
  return {
43494
- title: chartTitle,
44273
+ title: { text: chartTitle },
43495
44274
  type: "combo",
43496
44275
  dataSets: [
43497
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
43498
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
44276
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:barChart`), "comboChart"),
44277
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:lineChart`), "comboChart"),
43499
44278
  ],
43500
44279
  labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
43501
44280
  backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43502
44281
  default: "ffffff",
43503
44282
  }).asString(),
43504
- verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
43505
- default: "l",
43506
- }).asString() === "r"
43507
- ? "right"
43508
- : "left",
43509
44283
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
43510
44284
  default: "b",
43511
44285
  }).asString()],
@@ -43513,21 +44287,49 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
43513
44287
  fontColor: "000000",
43514
44288
  };
43515
44289
  }
43516
- extractChartDatasets(chartElement, chartType) {
43517
- if (chartType === "scatterChart") {
43518
- return this.extractScatterChartDatasets(chartElement);
43519
- }
43520
- return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
43521
- return {
43522
- label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
43523
- range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
43524
- };
43525
- });
44290
+ extractChartDatasets(chartElements, chartType) {
44291
+ return Array.from(chartElements)
44292
+ .map((element) => {
44293
+ if (chartType === "scatterChart") {
44294
+ return this.extractScatterChartDatasets(element);
44295
+ }
44296
+ return this.mapOnElements({ parent: element, query: "c:ser" }, (chartDataElement) => {
44297
+ let label = {};
44298
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44299
+ if (reference) {
44300
+ label = { reference };
44301
+ }
44302
+ else {
44303
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44304
+ if (text) {
44305
+ label = { text };
44306
+ }
44307
+ }
44308
+ return {
44309
+ label,
44310
+ range: this.extractChildTextContent(chartDataElement, "c:val c:f", {
44311
+ required: true,
44312
+ }),
44313
+ };
44314
+ });
44315
+ })
44316
+ .flat();
43526
44317
  }
43527
44318
  extractScatterChartDatasets(chartElement) {
43528
44319
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
44320
+ let label = {};
44321
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44322
+ if (reference) {
44323
+ label = { reference };
44324
+ }
44325
+ else {
44326
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44327
+ if (text) {
44328
+ label = { text };
44329
+ }
44330
+ }
43529
44331
  return {
43530
- label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
44332
+ label,
43531
44333
  range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
43532
44334
  };
43533
44335
  });
@@ -44259,7 +45061,7 @@ function getRelationFile(file, xmls) {
44259
45061
  return relsFile;
44260
45062
  }
44261
45063
 
44262
- const EXCEL_IMPORT_VERSION = 12;
45064
+ const EXCEL_IMPORT_VERSION = 16;
44263
45065
  class XlsxReader {
44264
45066
  warningManager;
44265
45067
  xmls;
@@ -44737,6 +45539,30 @@ const MIGRATIONS = [
44737
45539
  return data;
44738
45540
  },
44739
45541
  },
45542
+ {
45543
+ description: "transform chart data structure (2)",
45544
+ from: 16,
45545
+ to: 17,
45546
+ applyMigration(data) {
45547
+ for (const sheet of data.sheets || []) {
45548
+ for (const f in sheet.figures || []) {
45549
+ const figure = sheet.figures[f];
45550
+ if ("title" in figure.data && typeof figure.data.title === "string") {
45551
+ figure.data.title = { text: figure.data.title };
45552
+ }
45553
+ const figureType = figure.data.type;
45554
+ if (!["line", "bar", "pie", "scatter", "waterfall", "combo"].includes(figureType)) {
45555
+ continue;
45556
+ }
45557
+ const { dataSets, ...newData } = sheet.figures[f].data;
45558
+ const newDataSets = dataSets.map((dataRange) => ({ dataRange }));
45559
+ newData.dataSets = newDataSets;
45560
+ sheet.figures[f].data = newData;
45561
+ }
45562
+ }
45563
+ return data;
45564
+ },
45565
+ },
44740
45566
  ];
44741
45567
  /**
44742
45568
  * This function is used to repair faulty data independently of the migration.
@@ -48348,6 +49174,9 @@ class RangeAdapter {
48348
49174
  if (range.invalidXc) {
48349
49175
  return range.invalidXc;
48350
49176
  }
49177
+ if (!this.getters.tryGetSheet(range.sheetId)) {
49178
+ return CellErrorType.InvalidReference;
49179
+ }
48351
49180
  if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {
48352
49181
  return CellErrorType.InvalidReference;
48353
49182
  }
@@ -51766,7 +52595,6 @@ class PositionSet {
51766
52595
  *
51767
52596
  */
51768
52597
  class SpreadingRelation {
51769
- createEmptyPositionSet;
51770
52598
  /**
51771
52599
  * Internal structure:
51772
52600
  * For something like
@@ -51797,9 +52625,6 @@ class SpreadingRelation {
51797
52625
  */
51798
52626
  resultsToArrayFormulas = new PositionMap();
51799
52627
  arrayFormulasToResults = new PositionMap();
51800
- constructor(createEmptyPositionSet) {
51801
- this.createEmptyPositionSet = createEmptyPositionSet;
51802
- }
51803
52628
  getFormulaPositionsSpreadingOn(resultPosition) {
51804
52629
  return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
51805
52630
  }
@@ -51818,13 +52643,13 @@ class SpreadingRelation {
51818
52643
  */
51819
52644
  addRelation({ arrayFormulaPosition, resultPosition, }) {
51820
52645
  if (!this.resultsToArrayFormulas.has(resultPosition)) {
51821
- this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
52646
+ this.resultsToArrayFormulas.set(resultPosition, []);
51822
52647
  }
51823
- this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
52648
+ this.resultsToArrayFormulas.get(resultPosition)?.push(arrayFormulaPosition);
51824
52649
  if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
51825
- this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
52650
+ this.arrayFormulasToResults.set(arrayFormulaPosition, []);
51826
52651
  }
51827
- this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
52652
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.push(resultPosition);
51828
52653
  }
51829
52654
  hasArrayFormulaResult(position) {
51830
52655
  return this.resultsToArrayFormulas.has(position);
@@ -51845,7 +52670,7 @@ class Evaluator {
51845
52670
  evaluatedCells = new PositionMap();
51846
52671
  formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
51847
52672
  blockedArrayFormulas = new PositionSet({});
51848
- spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52673
+ spreadingRelations = new SpreadingRelation();
51849
52674
  constructor(context, getters) {
51850
52675
  this.context = context;
51851
52676
  this.getters = getters;
@@ -51939,7 +52764,7 @@ class Evaluator {
51939
52764
  }
51940
52765
  buildDependencyGraph() {
51941
52766
  this.blockedArrayFormulas = this.createEmptyPositionSet();
51942
- this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52767
+ this.spreadingRelations = new SpreadingRelation();
51943
52768
  this.formulaDependencies = lazy(() => {
51944
52769
  const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
51945
52770
  .filter((range) => !range.invalidSheetName && !range.invalidXc)
@@ -52071,6 +52896,7 @@ class Evaluator {
52071
52896
  const nbColumns = formulaReturn.length;
52072
52897
  const nbRows = formulaReturn[0].length;
52073
52898
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52899
+ this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52074
52900
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
52075
52901
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
52076
52902
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
@@ -52093,6 +52919,18 @@ class Evaluator {
52093
52919
  }
52094
52920
  throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
52095
52921
  }
52922
+ assertNoMergedCellsInSpreadZone({ sheetId, col, row }, matrixResult) {
52923
+ const mergedCells = this.getters.getMergesInZone(sheetId, {
52924
+ top: row,
52925
+ bottom: row + matrixResult.length,
52926
+ left: col,
52927
+ right: col + matrixResult[0].length,
52928
+ });
52929
+ if (mergedCells.length === 0) {
52930
+ return;
52931
+ }
52932
+ throw new SplillBlockedError(_t("Merged cells found in the spill zone. Please unmerge cells before using array formulas."));
52933
+ }
52096
52934
  updateSpreadRelation({ sheetId, col, row, }) {
52097
52935
  const arrayFormulaPosition = { sheetId, col, row };
52098
52936
  return (i, j) => {
@@ -52330,10 +53168,6 @@ class EvaluationPlugin extends UIPlugin {
52330
53168
  this.evaluator.updateDependencies(cmd);
52331
53169
  }
52332
53170
  break;
52333
- case "DUPLICATE_SHEET":
52334
- case "CREATE_SHEET":
52335
- this.shouldRebuildDependenciesGraph = true;
52336
- break;
52337
53171
  case "EVALUATE_CELLS":
52338
53172
  this.evaluator.evaluateAllCells();
52339
53173
  break;
@@ -52434,16 +53268,22 @@ class EvaluationPlugin extends UIPlugin {
52434
53268
  let newContent = undefined;
52435
53269
  let newFormat = undefined;
52436
53270
  let isExported = true;
53271
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52437
53272
  const formulaCell = this.getCorrespondingFormulaCell(position);
52438
53273
  if (formulaCell) {
52439
53274
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
52440
53275
  isFormula = isExported;
52441
53276
  if (!isExported) {
52442
- newContent = (value ?? "").toString();
52443
- newFormat = evaluatedCell.format;
53277
+ // If the cell contains a non-exported formula and that is evaluates to
53278
+ // nothing* ,we don't export it.
53279
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
53280
+ // the empty string.
53281
+ if (value !== "") {
53282
+ newContent = (value ?? "").toString();
53283
+ newFormat = evaluatedCell.format;
53284
+ }
52444
53285
  }
52445
53286
  }
52446
- const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52447
53287
  const exportedCellData = exportedSheetData.cells[xc] || {};
52448
53288
  const format = newFormat
52449
53289
  ? getItemId(newFormat, data.formats)
@@ -57066,7 +57906,7 @@ class ClipboardPlugin extends UIPlugin {
57066
57906
  paintFormatStatus = "inactive";
57067
57907
  originSheetId;
57068
57908
  copiedData;
57069
- _isCutOperation;
57909
+ _isCutOperation = false;
57070
57910
  // ---------------------------------------------------------------------------
57071
57911
  // Command Handling
57072
57912
  // ---------------------------------------------------------------------------
@@ -57078,14 +57918,17 @@ class ClipboardPlugin extends UIPlugin {
57078
57918
  case "PASTE_FROM_OS_CLIPBOARD": {
57079
57919
  const copiedData = this.convertOSClipboardData(cmd.text);
57080
57920
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57081
- return this.isPasteAllowed(cmd.target, copiedData, { pasteOption });
57921
+ return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
57082
57922
  }
57083
57923
  case "PASTE": {
57084
57924
  if (!this.copiedData) {
57085
57925
  return "EmptyClipboard" /* CommandResult.EmptyClipboard */;
57086
57926
  }
57087
57927
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57088
- return this.isPasteAllowed(cmd.target, this.copiedData, { pasteOption });
57928
+ return this.isPasteAllowed(cmd.target, this.copiedData, {
57929
+ pasteOption: pasteOption,
57930
+ isCutOperation: this._isCutOperation,
57931
+ });
57089
57932
  }
57090
57933
  case "COPY_PASTE_CELLS_ABOVE": {
57091
57934
  const zones = this.getters.getSelectedZones();
@@ -57103,13 +57946,13 @@ class ClipboardPlugin extends UIPlugin {
57103
57946
  }
57104
57947
  case "INSERT_CELL": {
57105
57948
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57106
- const copiedData = this.copy("CUT", cut);
57107
- return this.isPasteAllowed(paste, copiedData, {});
57949
+ const copiedData = this.copy(cut);
57950
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57108
57951
  }
57109
57952
  case "DELETE_CELL": {
57110
57953
  const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
57111
- const copiedData = this.copy("CUT", cut);
57112
- return this.isPasteAllowed(paste, copiedData, {});
57954
+ const copiedData = this.copy(cut);
57955
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57113
57956
  }
57114
57957
  case "ACTIVATE_PAINT_FORMAT": {
57115
57958
  if (this.paintFormatStatus !== "inactive") {
@@ -57127,23 +57970,27 @@ class ClipboardPlugin extends UIPlugin {
57127
57970
  const zones = this.getters.getSelectedZones();
57128
57971
  this.status = "visible";
57129
57972
  this.originSheetId = this.getters.getActiveSheetId();
57130
- this.copiedData = this.copy(cmd.type, zones);
57973
+ this.copiedData = this.copy(zones);
57974
+ this._isCutOperation = cmd.type === "CUT";
57131
57975
  break;
57132
57976
  case "PASTE_FROM_OS_CLIPBOARD": {
57977
+ this._isCutOperation = false;
57133
57978
  this.copiedData = this.convertOSClipboardData(cmd.text);
57134
57979
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57135
- this.paste(cmd.target, {
57980
+ this.paste(cmd.target, this.copiedData, {
57136
57981
  pasteOption,
57137
57982
  selectTarget: true,
57983
+ isCutOperation: false,
57138
57984
  });
57139
57985
  this.status = "invisible";
57140
57986
  break;
57141
57987
  }
57142
57988
  case "PASTE": {
57143
57989
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57144
- this.paste(cmd.target, {
57990
+ this.paste(cmd.target, this.copiedData, {
57145
57991
  pasteOption,
57146
57992
  selectTarget: true,
57993
+ isCutOperation: this._isCutOperation,
57147
57994
  });
57148
57995
  if (this.paintFormatStatus === "oneOff") {
57149
57996
  this.paintFormatStatus = "inactive";
@@ -57164,9 +58011,9 @@ class ClipboardPlugin extends UIPlugin {
57164
58011
  top: multipleRowsInSelection ? zone.top : zone.top - 1,
57165
58012
  };
57166
58013
  this.originSheetId = this.getters.getActiveSheetId();
57167
- this.copiedData = this.copy("COPY", [copyTarget]);
57168
- this.paste([zone], {
57169
- pasteOption: undefined,
58014
+ const copiedData = this.copy([copyTarget]);
58015
+ this.paste([zone], copiedData, {
58016
+ isCutOperation: false,
57170
58017
  selectTarget: true,
57171
58018
  });
57172
58019
  }
@@ -57181,9 +58028,9 @@ class ClipboardPlugin extends UIPlugin {
57181
58028
  left: multipleColsInSelection ? zone.left : zone.left - 1,
57182
58029
  };
57183
58030
  this.originSheetId = this.getters.getActiveSheetId();
57184
- this.copiedData = this.copy("COPY", [copyTarget]);
57185
- this.paste([zone], {
57186
- pasteOption: undefined,
58031
+ const copiedData = this.copy([copyTarget]);
58032
+ this.paste([zone], copiedData, {
58033
+ isCutOperation: false,
57187
58034
  selectTarget: true,
57188
58035
  });
57189
58036
  }
@@ -57199,14 +58046,14 @@ class ClipboardPlugin extends UIPlugin {
57199
58046
  }
57200
58047
  break;
57201
58048
  }
57202
- this.copiedData = this.copy("CUT", cut);
57203
- this.paste(paste, {});
58049
+ const copiedData = this.copy(cut);
58050
+ this.paste(paste, copiedData, { isCutOperation: true });
57204
58051
  break;
57205
58052
  }
57206
58053
  case "INSERT_CELL": {
57207
58054
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57208
- this.copiedData = this.copy("CUT", cut);
57209
- this.paste(paste, {});
58055
+ const copiedData = this.copy(cut);
58056
+ this.paste(paste, copiedData, { isCutOperation: true });
57210
58057
  break;
57211
58058
  }
57212
58059
  case "ADD_COLUMNS_ROWS": {
@@ -57238,7 +58085,8 @@ class ClipboardPlugin extends UIPlugin {
57238
58085
  break;
57239
58086
  }
57240
58087
  case "REPEAT_PASTE": {
57241
- this.paste(cmd.target, {
58088
+ this.paste(cmd.target, this.copiedData, {
58089
+ isCutOperation: false,
57242
58090
  pasteOption: cmd.pasteOption,
57243
58091
  selectTarget: true,
57244
58092
  });
@@ -57246,7 +58094,7 @@ class ClipboardPlugin extends UIPlugin {
57246
58094
  }
57247
58095
  case "ACTIVATE_PAINT_FORMAT": {
57248
58096
  const zones = this.getters.getSelectedZones();
57249
- this.copiedData = this.copy("COPY", zones);
58097
+ this.copiedData = this.copy(zones);
57250
58098
  this.status = "visible";
57251
58099
  if (cmd.persistent) {
57252
58100
  this.paintFormatStatus = "persistent";
@@ -57277,7 +58125,6 @@ class ClipboardPlugin extends UIPlugin {
57277
58125
  }
57278
58126
  }
57279
58127
  convertOSClipboardData(clipboardData) {
57280
- this._isCutOperation = false;
57281
58128
  const handlers = clipboardHandlersRegistries.figureHandlers
57282
58129
  .getAll()
57283
58130
  .map((handler) => new handler(this.getters, this.dispatch));
@@ -57315,7 +58162,6 @@ class ClipboardPlugin extends UIPlugin {
57315
58162
  for (const handler of this.selectClipboardHandlers(copiedData)) {
57316
58163
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
57317
58164
  ...options,
57318
- isCutOperation: this.isCutOperation(),
57319
58165
  });
57320
58166
  if (result !== "Success" /* CommandResult.Success */) {
57321
58167
  return result;
@@ -57338,9 +58184,8 @@ class ClipboardPlugin extends UIPlugin {
57338
58184
  }
57339
58185
  return false;
57340
58186
  }
57341
- copy(operation, zones) {
58187
+ copy(zones) {
57342
58188
  let copiedData = {};
57343
- this._isCutOperation = operation === "CUT";
57344
58189
  const clipboardData = this.getClipboardData(zones);
57345
58190
  for (const handler of this.selectClipboardHandlers(clipboardData)) {
57346
58191
  const data = handler.copy(clipboardData);
@@ -57348,8 +58193,8 @@ class ClipboardPlugin extends UIPlugin {
57348
58193
  }
57349
58194
  return copiedData;
57350
58195
  }
57351
- paste(zones, options) {
57352
- if (!this.copiedData) {
58196
+ paste(zones, copiedData, options) {
58197
+ if (!copiedData) {
57353
58198
  return;
57354
58199
  }
57355
58200
  let zone = undefined;
@@ -57357,12 +58202,9 @@ class ClipboardPlugin extends UIPlugin {
57357
58202
  let target = {
57358
58203
  zones,
57359
58204
  };
57360
- const handlers = this.selectClipboardHandlers(this.copiedData);
58205
+ const handlers = this.selectClipboardHandlers(copiedData);
57361
58206
  for (const handler of handlers) {
57362
- const currentTarget = handler.getPasteTarget(zones, this.copiedData, {
57363
- ...options,
57364
- isCutOperation: this.isCutOperation(),
57365
- });
58207
+ const currentTarget = handler.getPasteTarget(zones, copiedData, options);
57366
58208
  if (currentTarget.figureId) {
57367
58209
  target.figureId = currentTarget.figureId;
57368
58210
  }
@@ -57378,7 +58220,7 @@ class ClipboardPlugin extends UIPlugin {
57378
58220
  if (zone !== undefined) {
57379
58221
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
57380
58222
  }
57381
- handlers.forEach((handler) => handler.paste(target, this.copiedData, { ...options, isCutOperation: this.isCutOperation() }));
58223
+ handlers.forEach((handler) => handler.paste(target, copiedData, options));
57382
58224
  if (!options?.selectTarget) {
57383
58225
  return;
57384
58226
  }
@@ -59764,6 +60606,7 @@ class BottomBarSheet extends owl.Component {
59764
60606
  sheetDivRef = owl.useRef("sheetDiv");
59765
60607
  sheetNameRef = owl.useRef("sheetNameSpan");
59766
60608
  editionState = "initializing";
60609
+ DOMFocusableElementStore;
59767
60610
  setup() {
59768
60611
  owl.onMounted(() => {
59769
60612
  if (this.isSheetActive) {
@@ -59776,6 +60619,7 @@ class BottomBarSheet extends owl.Component {
59776
60619
  this.focusInputAndSelectContent();
59777
60620
  }
59778
60621
  });
60622
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
59779
60623
  }
59780
60624
  focusInputAndSelectContent() {
59781
60625
  if (!this.state.isEditing || !this.sheetNameRef.el)
@@ -59817,9 +60661,11 @@ class BottomBarSheet extends owl.Component {
59817
60661
  if (ev.key === "Enter") {
59818
60662
  ev.preventDefault();
59819
60663
  this.stopEdition();
60664
+ this.DOMFocusableElementStore.focus();
59820
60665
  }
59821
60666
  if (ev.key === "Escape") {
59822
60667
  this.cancelEdition();
60668
+ this.DOMFocusableElementStore.focus();
59823
60669
  }
59824
60670
  }
59825
60671
  onClickSheetName(ev) {
@@ -61868,6 +62714,9 @@ class Spreadsheet extends owl.Component {
61868
62714
  static template = "o-spreadsheet-Spreadsheet";
61869
62715
  static props = {
61870
62716
  model: Object,
62717
+ notifyUser: { type: Function, optional: true },
62718
+ raiseError: { type: Function, optional: true },
62719
+ askConfirmation: { type: Function, optional: true },
61871
62720
  };
61872
62721
  static components = {
61873
62722
  TopBar,
@@ -61914,7 +62763,11 @@ class Spreadsheet extends owl.Component {
61914
62763
  toggleSidePanel: this.sidePanel.toggle.bind(this.sidePanel),
61915
62764
  clipboard: this.env.clipboard || instantiateClipboard(),
61916
62765
  startCellEdition: (content) => this.composerFocusStore.focusGridComposerCell(content),
62766
+ notifyUser: (notification) => this.notificationStore.notifyUser(notification),
62767
+ askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
62768
+ raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
61917
62769
  });
62770
+ this.notificationStore.updateNotificationCallbacks({ ...this.props });
61918
62771
  owl.useEffect(() => {
61919
62772
  /**
61920
62773
  * Only refocus the grid if the active element is not a child of the spreadsheet
@@ -61937,6 +62790,11 @@ class Spreadsheet extends owl.Component {
61937
62790
  if (nextProps.model !== this.props.model) {
61938
62791
  throw new Error("Changing the props model is not supported at the moment.");
61939
62792
  }
62793
+ if (nextProps.notifyUser !== this.props.notifyUser ||
62794
+ nextProps.askConfirmation !== this.props.askConfirmation ||
62795
+ nextProps.raiseError !== this.props.raiseError) {
62796
+ this.notificationStore.updateNotificationCallbacks({ ...nextProps });
62797
+ }
61940
62798
  });
61941
62799
  const render = batched(this.render.bind(this, true));
61942
62800
  owl.onMounted(() => {
@@ -61954,7 +62812,7 @@ class Spreadsheet extends owl.Component {
61954
62812
  bindModelEvents() {
61955
62813
  this.model.on("update", this, () => this.render(true));
61956
62814
  this.model.on("notify-ui", this, (notification) => this.notificationStore.notifyUser(notification));
61957
- this.model.on("raise-error-ui", this, ({ text }) => this.env.raiseError(text));
62815
+ this.model.on("raise-error-ui", this, ({ text }) => this.notificationStore.raiseError(text));
61958
62816
  }
61959
62817
  unbindModelEvents() {
61960
62818
  this.model.off("update", this);
@@ -63509,10 +64367,13 @@ function createChart(chart, chartSheetIndex, data) {
63509
64367
  });
63510
64368
  // <manualLayout/> to manually position the chart in the figure container
63511
64369
  let title = escapeXml ``;
63512
- if (chart.data.title) {
64370
+ if (chart.data.title?.text) {
64371
+ const color = chart.data.title.color
64372
+ ? toXlsxHexColor(chart.data.title.color)
64373
+ : chart.data.fontColor;
63513
64374
  title = escapeXml /*xml*/ `
63514
64375
  <c:title>
63515
- ${insertText(chart.data.title, chart.data.fontColor)}
64376
+ ${insertText(chart.data.title.text, color, DEFAULT_CHART_FONT_SIZE, chart.data.title)}
63516
64377
  <c:overlay val="0" />
63517
64378
  </c:title>
63518
64379
  `;
@@ -63600,7 +64461,7 @@ function lineAttributes(params) {
63600
64461
  </a:ln>
63601
64462
  `;
63602
64463
  }
63603
- function insertText(text, fontColor = "000000", fontsize = 22) {
64464
+ function insertText(text, fontColor = "000000", fontsize = DEFAULT_CHART_FONT_SIZE, style = {}) {
63604
64465
  return escapeXml /*xml*/ `
63605
64466
  <c:tx>
63606
64467
  <c:rich>
@@ -63608,13 +64469,13 @@ function insertText(text, fontColor = "000000", fontsize = 22) {
63608
64469
  <a:lstStyle />
63609
64470
  <a:p>
63610
64471
  <a:pPr lvl="0">
63611
- <a:defRPr b="0">
64472
+ <a:defRPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}">
63612
64473
  ${solidFill(fontColor)}
63613
64474
  <a:latin typeface="+mn-lt"/>
63614
64475
  </a:defRPr>
63615
64476
  </a:pPr>
63616
64477
  <a:r> <!-- Runs -->
63617
- <a:rPr sz="${fontsize * 100}"/>
64478
+ <a:rPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}" sz="${fontsize * 100}"/>
63618
64479
  <a:t>${text}</a:t>
63619
64480
  </a:r>
63620
64481
  </a:p>
@@ -63643,6 +64504,24 @@ function insertTextProperties(fontsize = 12, fontColor = "000000", bold = false,
63643
64504
  </c:txPr>
63644
64505
  `;
63645
64506
  }
64507
+ function extractDataSetLabel(label) {
64508
+ if (!label) {
64509
+ return escapeXml /*xml*/ ``;
64510
+ }
64511
+ if ("text" in label && label.text) {
64512
+ return escapeXml /*xml*/ `
64513
+ <c:tx><c:v>${label.text}</c:v></c:tx>
64514
+ `;
64515
+ }
64516
+ if ("reference" in label && label.reference) {
64517
+ return escapeXml /*xml*/ `
64518
+ <c:tx>
64519
+ ${stringRef(label.reference)}
64520
+ </c:tx>
64521
+ `;
64522
+ }
64523
+ return escapeXml /*xml*/ ``;
64524
+ }
63646
64525
  function addBarChart(chart) {
63647
64526
  // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
63648
64527
  // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
@@ -63650,46 +64529,72 @@ function addBarChart(chart) {
63650
64529
  //
63651
64530
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63652
64531
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
63653
- const colors = new ChartColors();
63654
- const dataSetsNodes = [];
64532
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64533
+ const colors = new ColorGenerator(dataSetsColors);
64534
+ const leftDataSetsNodes = [];
64535
+ const rightDataSetsNodes = [];
63655
64536
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63656
64537
  const color = toXlsxHexColor(colors.next());
63657
64538
  const dataShapeProperty = shapeProperty({
63658
64539
  backgroundColor: color,
63659
64540
  line: { color },
63660
64541
  });
63661
- dataSetsNodes.push(escapeXml /*xml*/ `
64542
+ const dataSetNode = escapeXml /*xml*/ `
63662
64543
  <c:ser>
63663
64544
  <c:idx val="${dsIndex}"/>
63664
64545
  <c:order val="${dsIndex}"/>
63665
- ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64546
+ ${extractDataSetLabel(dataset.label)}
63666
64547
  ${dataShapeProperty}
63667
64548
  ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63668
64549
  <c:val> <!-- x-coordinate values -->
63669
64550
  ${numberRef(dataset.range)}
63670
64551
  </c:val>
63671
64552
  </c:ser>
63672
- `);
64553
+ `;
64554
+ if (dataset.rightYAxis) {
64555
+ rightDataSetsNodes.push(dataSetNode);
64556
+ }
64557
+ else {
64558
+ leftDataSetsNodes.push(dataSetNode);
64559
+ }
63673
64560
  }
63674
- // Excel does not support this feature
63675
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63676
64561
  const grouping = chart.stacked ? "stacked" : "clustered";
63677
64562
  const overlap = chart.stacked ? 100 : -20;
63678
64563
  return escapeXml /*xml*/ `
63679
- <c:barChart>
63680
- <c:barDir val="col"/>
63681
- <c:grouping val="${grouping}"/>
63682
- <c:overlap val="${overlap}"/>
63683
- <c:gapWidth val="70"/>
63684
- <!-- each data marker in the series does not have a different color -->
63685
- <c:varyColors val="0"/>
63686
- ${joinXmlNodes(dataSetsNodes)}
63687
- <c:axId val="${catAxId}" />
63688
- <c:axId val="${valAxId}" />
63689
- </c:barChart>
63690
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63691
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
63692
- `;
64564
+ ${leftDataSetsNodes.length
64565
+ ? escapeXml /*xml*/ `
64566
+ <c:barChart>
64567
+ <c:barDir val="col"/>
64568
+ <c:grouping val="${grouping}"/>
64569
+ <c:overlap val="${overlap}"/>
64570
+ <c:gapWidth val="70"/>
64571
+ <!-- each data marker in the series does not have a different color -->
64572
+ <c:varyColors val="0"/>
64573
+ ${joinXmlNodes(leftDataSetsNodes)}
64574
+ <c:axId val="${catAxId}" />
64575
+ <c:axId val="${valAxId}" />
64576
+ </c:barChart>
64577
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64578
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64579
+ `
64580
+ : ""}
64581
+ ${rightDataSetsNodes.length
64582
+ ? escapeXml /*xml*/ `
64583
+ <c:barChart>
64584
+ <c:barDir val="col"/>
64585
+ <c:grouping val="${grouping}"/>
64586
+ <c:overlap val="${overlap}"/>
64587
+ <c:gapWidth val="70"/>
64588
+ <!-- each data marker in the series does not have a different color -->
64589
+ <c:varyColors val="0"/>
64590
+ ${joinXmlNodes(rightDataSetsNodes)}
64591
+ <c:axId val="${catAxId + 1}" />
64592
+ <c:axId val="${valAxId + 1}" />
64593
+ </c:barChart>
64594
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64595
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64596
+ `
64597
+ : ""}`;
63693
64598
  }
63694
64599
  function addComboChart(chart) {
63695
64600
  // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
@@ -63698,28 +64603,38 @@ function addComboChart(chart) {
63698
64603
  //
63699
64604
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63700
64605
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
63701
- const colors = new ChartColors();
63702
- const dataSetsNodes = [];
63703
- for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
64606
+ const dataSets = chart.dataSets;
64607
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
64608
+ const colors = new ColorGenerator(dataSetsColors);
64609
+ let dataSet = dataSets[0];
64610
+ const firstColor = toXlsxHexColor(colors.next());
64611
+ const useRightAxisForBarSerie = dataSet.rightYAxis ?? false;
64612
+ const barDataSetNode = escapeXml /*xml*/ `
64613
+ <c:ser>
64614
+ <c:idx val="0"/>
64615
+ <c:order val="0"/>
64616
+ ${extractDataSetLabel(dataSet.label)}
64617
+ ${shapeProperty({
64618
+ backgroundColor: firstColor,
64619
+ line: { color: firstColor },
64620
+ })}
64621
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64622
+ <!-- x-coordinate values -->
64623
+ <c:val>
64624
+ ${numberRef(dataSet.range)}
64625
+ </c:val>
64626
+ </c:ser>
64627
+ `;
64628
+ const leftDataSetsNodes = [];
64629
+ const rightDataSetsNodes = [];
64630
+ for (let dsIndex = 1; dsIndex < dataSets.length; dsIndex++) {
64631
+ dataSet = dataSets[dsIndex];
63704
64632
  const color = toXlsxHexColor(colors.next());
63705
64633
  const dataShapeProperty = shapeProperty({
63706
64634
  backgroundColor: color,
63707
64635
  line: { color },
63708
64636
  });
63709
- dataSetsNodes.push(dsIndex === "0"
63710
- ? escapeXml /*xml*/ `
63711
- <c:ser>
63712
- <c:idx val="${dsIndex}"/>
63713
- <c:order val="${dsIndex}"/>
63714
- ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
63715
- ${dataShapeProperty}
63716
- ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63717
- <c:val> <!-- x-coordinate values -->
63718
- ${numberRef(dataset.range)}
63719
- </c:val>
63720
- </c:ser>
63721
- `
63722
- : escapeXml /*xml*/ `
64637
+ const dataSetNode = escapeXml /*xml*/ `
63723
64638
  <c:ser>
63724
64639
  <c:idx val="${dsIndex}"/>
63725
64640
  <c:order val="${dsIndex}"/>
@@ -63727,18 +64642,24 @@ function addComboChart(chart) {
63727
64642
  <c:marker>
63728
64643
  <c:symbol val="circle" />
63729
64644
  <c:size val="5"/>
64645
+ ${dataShapeProperty}
63730
64646
  </c:marker>
63731
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64647
+ ${extractDataSetLabel(dataSet.label)}
63732
64648
  ${dataShapeProperty}
63733
- ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63734
- <c:val> <!-- x-coordinate values -->
63735
- ${numberRef(dataset.range)}
64649
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64650
+ <!-- x-coordinate values -->
64651
+ <c:val>
64652
+ ${numberRef(dataSet.range)}
63736
64653
  </c:val>
63737
64654
  </c:ser>
63738
- `);
64655
+ `;
64656
+ if (dataSet.rightYAxis) {
64657
+ rightDataSetsNodes.push(dataSetNode);
64658
+ }
64659
+ else {
64660
+ leftDataSetsNodes.push(dataSetNode);
64661
+ }
63739
64662
  }
63740
- // Excel does not support this feature
63741
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63742
64663
  const overlap = chart.stacked ? 100 : -20;
63743
64664
  return escapeXml /*xml*/ `
63744
64665
  <c:barChart>
@@ -63748,34 +64669,63 @@ function addComboChart(chart) {
63748
64669
  <c:gapWidth val="70"/>
63749
64670
  <!-- each data marker in the series does not have a different color -->
63750
64671
  <c:varyColors val="0"/>
63751
- ${dataSetsNodes[0]}
63752
- <c:axId val="${catAxId}" />
63753
- <c:axId val="${valAxId}" />
64672
+ ${barDataSetNode}
64673
+ <c:axId val="${catAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
64674
+ <c:axId val="${valAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
63754
64675
  </c:barChart>
63755
- <c:lineChart>
63756
- <c:grouping val="standard"/>
63757
- <!-- each data marker in the series does not have a different color -->
63758
- <c:varyColors val="0"/>
63759
- ${joinXmlNodes(dataSetsNodes.slice(1))}
63760
- <c:axId val="${catAxId}" />
63761
- <c:axId val="${valAxId}" />
63762
- </c:lineChart>
63763
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63764
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
64676
+ ${leftDataSetsNodes.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(leftDataSetsNodes)}
64683
+ <c:axId val="${catAxId}" />
64684
+ <c:axId val="${valAxId}" />
64685
+ </c:lineChart>
64686
+ `
64687
+ : ""}
64688
+ ${rightDataSetsNodes.length
64689
+ ? escapeXml /*xml*/ `
64690
+ <c:lineChart>
64691
+ <c:grouping val="standard"/>
64692
+ <!-- each data marker in the series does not have a different color -->
64693
+ <c:varyColors val="0"/>
64694
+ ${joinXmlNodes(rightDataSetsNodes)}
64695
+ <c:axId val="${catAxId + 1}" />
64696
+ <c:axId val="${valAxId + 1}" />
64697
+ </c:lineChart>
64698
+ `
64699
+ : ""}
64700
+ ${!useRightAxisForBarSerie || leftDataSetsNodes.length
64701
+ ? escapeXml /*xml*/ `
64702
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64703
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64704
+ `
64705
+ : ""}
64706
+ ${useRightAxisForBarSerie || rightDataSetsNodes.length
64707
+ ? escapeXml /*xml*/ `
64708
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length || !useRightAxisForBarSerie ? 1 : 0)}
64709
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64710
+ `
64711
+ : ""}
63765
64712
  `;
63766
64713
  }
63767
64714
  function addLineChart(chart) {
63768
- const colors = new ChartColors();
63769
- const dataSetsNodes = [];
64715
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64716
+ const colors = new ColorGenerator(dataSetsColors);
64717
+ const leftDataSetsNodes = [];
64718
+ const rightDataSetsNodes = [];
63770
64719
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
64720
+ const color = toXlsxHexColor(colors.next());
63771
64721
  const dataShapeProperty = shapeProperty({
63772
64722
  line: {
63773
64723
  width: 2.5,
63774
64724
  style: "solid",
63775
- color: toXlsxHexColor(colors.next()),
64725
+ color,
63776
64726
  },
63777
64727
  });
63778
- dataSetsNodes.push(escapeXml /*xml*/ `
64728
+ const dataSetNode = escapeXml /*xml*/ `
63779
64729
  <c:ser>
63780
64730
  <c:idx val="${dsIndex}"/>
63781
64731
  <c:order val="${dsIndex}"/>
@@ -63783,37 +64733,63 @@ function addLineChart(chart) {
63783
64733
  <c:marker>
63784
64734
  <c:symbol val="circle" />
63785
64735
  <c:size val="5"/>
64736
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63786
64737
  </c:marker>
63787
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64738
+ ${extractDataSetLabel(dataset.label)}
63788
64739
  ${dataShapeProperty}
63789
64740
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63790
64741
  <c:val> <!-- x-coordinate values -->
63791
64742
  ${numberRef(dataset.range)}
63792
64743
  </c:val>
63793
64744
  </c:ser>
63794
- `);
64745
+ `;
64746
+ if (dataset.rightYAxis) {
64747
+ rightDataSetsNodes.push(dataSetNode);
64748
+ }
64749
+ else {
64750
+ leftDataSetsNodes.push(dataSetNode);
64751
+ }
63795
64752
  }
63796
- // Excel does not support this feature
63797
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63798
64753
  const grouping = chart.stacked ? "stacked" : "standard";
63799
64754
  return escapeXml /*xml*/ `
63800
- <c:lineChart>
63801
- <c:grouping val="${grouping}"/>
63802
- <!-- each data marker in the series does not have a different color -->
63803
- <c:varyColors val="0"/>
63804
- ${joinXmlNodes(dataSetsNodes)}
63805
- <c:axId val="${catAxId}" />
63806
- <c:axId val="${valAxId}" />
63807
- </c:lineChart>
63808
- ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63809
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
64755
+ ${leftDataSetsNodes.length
64756
+ ? escapeXml /*xml*/ `
64757
+ <c:lineChart>
64758
+ <c:grouping val="${grouping}"/>
64759
+ <!-- each data marker in the series does not have a different color -->
64760
+ <c:varyColors val="0"/>
64761
+ ${joinXmlNodes(leftDataSetsNodes)}
64762
+ <c:axId val="${catAxId}" />
64763
+ <c:axId val="${valAxId}" />
64764
+ </c:lineChart>
64765
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64766
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64767
+ `
64768
+ : ""}
64769
+ ${rightDataSetsNodes.length
64770
+ ? escapeXml /*xml*/ `
64771
+ <c:lineChart>
64772
+ <c:grouping val="${grouping}"/>
64773
+ <!-- each data marker in the series does not have a different color -->
64774
+ <c:varyColors val="0"/>
64775
+ ${joinXmlNodes(rightDataSetsNodes)}
64776
+ <c:axId val="${catAxId + 1}" />
64777
+ <c:axId val="${valAxId + 1}" />
64778
+ </c:lineChart>
64779
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64780
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64781
+ `
64782
+ : ""}
63810
64783
  `;
63811
64784
  }
63812
64785
  function addScatterChart(chart) {
63813
- const colors = new ChartColors();
63814
- const dataSetsNodes = [];
64786
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64787
+ const colors = new ColorGenerator(dataSetsColors);
64788
+ const leftDataSetsNodes = [];
64789
+ const rightDataSetsNodes = [];
63815
64790
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63816
- dataSetsNodes.push(escapeXml /*xml*/ `
64791
+ const color = toXlsxHexColor(colors.next());
64792
+ const dataSetNode = escapeXml /*xml*/ `
63817
64793
  <c:ser>
63818
64794
  <c:idx val="${dsIndex}"/>
63819
64795
  <c:order val="${dsIndex}"/>
@@ -63828,8 +64804,9 @@ function addScatterChart(chart) {
63828
64804
  <c:marker>
63829
64805
  <c:symbol val="circle" />
63830
64806
  <c:size val="5"/>
63831
- ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
64807
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63832
64808
  </c:marker>
64809
+ ${extractDataSetLabel(dataset.label)}
63833
64810
  ${chart.labelRange
63834
64811
  ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
63835
64812
  ${numberRef(chart.labelRange)}
@@ -63839,24 +64816,46 @@ function addScatterChart(chart) {
63839
64816
  ${numberRef(dataset.range)}
63840
64817
  </c:yVal>
63841
64818
  </c:ser>
63842
- `);
64819
+ `;
64820
+ if (dataset.rightYAxis) {
64821
+ rightDataSetsNodes.push(dataSetNode);
64822
+ }
64823
+ else {
64824
+ leftDataSetsNodes.push(dataSetNode);
64825
+ }
63843
64826
  }
63844
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63845
64827
  return escapeXml /*xml*/ `
63846
- <c:scatterChart>
63847
- <!-- each data marker in the series does not have a different color -->
63848
- <c:varyColors val="0"/>
63849
- <c:scatterStyle val="lineMarker"/>
63850
- ${joinXmlNodes(dataSetsNodes)}
63851
- <c:axId val="${catAxId}" />
63852
- <c:axId val="${valAxId}" />
63853
- </c:scatterChart>
63854
- ${addAx("b", "c:valAx", catAxId, valAxId, { fontColor: chart.fontColor })}
63855
- ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
63856
- `;
64828
+ ${leftDataSetsNodes.length
64829
+ ? escapeXml /*xml*/ `
64830
+ <c:scatterChart>
64831
+ <!-- each data marker in the series does not have a different color -->
64832
+ <c:varyColors val="0"/>
64833
+ <c:scatterStyle val="lineMarker"/>
64834
+ ${joinXmlNodes(leftDataSetsNodes)}
64835
+ <c:axId val="${catAxId}" />
64836
+ <c:axId val="${valAxId}" />
64837
+ </c:scatterChart>
64838
+ ${addAx("b", "c:valAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64839
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64840
+ `
64841
+ : ""}
64842
+ ${rightDataSetsNodes.length
64843
+ ? escapeXml /*xml*/ `
64844
+ <c:scatterChart>
64845
+ <!-- each data marker in the series does not have a different color -->
64846
+ <c:varyColors val="0"/>
64847
+ <c:scatterStyle val="lineMarker"/>
64848
+ ${joinXmlNodes(rightDataSetsNodes)}
64849
+ <c:axId val="${catAxId + 1}" />
64850
+ <c:axId val="${valAxId + 1}" />
64851
+ </c:scatterChart>
64852
+ ${addAx("b", "c:valAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64853
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64854
+ `
64855
+ : ""}`;
63857
64856
  }
63858
64857
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
63859
- const colors = new ChartColors();
64858
+ const colors = new ColorGenerator();
63860
64859
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
63861
64860
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
63862
64861
  const dataSetsNodes = [];
@@ -63880,7 +64879,7 @@ function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSiz
63880
64879
  <c:ser>
63881
64880
  <c:idx val="${dsIndex}"/>
63882
64881
  <c:order val="${dsIndex}"/>
63883
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64882
+ ${extractDataSetLabel(dataset.label)}
63884
64883
  ${joinXmlNodes(dataPoints)}
63885
64884
  ${insertDataLabels({ showLeaderLines: true })}
63886
64885
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
@@ -63912,14 +64911,16 @@ function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {
63912
64911
  </dLbls>
63913
64912
  `;
63914
64913
  }
63915
- function addAx(position, axisName, axId, crossAxId, { fontColor }) {
64914
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
63916
64915
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
63917
64916
  // I.e. x-axis, will reference y-axis and vice-versa.
64917
+ const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
63918
64918
  return escapeXml /*xml*/ `
63919
64919
  <${axisName}>
63920
64920
  <c:axId val="${axId}"/>
63921
64921
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
63922
- <c:delete val="0"/> <!-- by default, axis are not displayed -->
64922
+ <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
64923
+ <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
63923
64924
  <c:scaling>
63924
64925
  <c:orientation val="minMax" />
63925
64926
  </c:scaling>
@@ -63929,9 +64930,9 @@ function addAx(position, axisName, axId, crossAxId, { fontColor }) {
63929
64930
  <c:minorTickMark val="none" />
63930
64931
  <c:numFmt formatCode="General" sourceLinked="1" />
63931
64932
  <c:title>
63932
- ${insertText("")}
64933
+ ${insertText(title?.text ?? "", color, 10, title)}
63933
64934
  </c:title>
63934
- ${insertTextProperties(10, fontColor)}
64935
+ ${insertTextProperties(10, defaultFontColor)}
63935
64936
  </${axisName}>
63936
64937
  <!-- <tickLblPos/> omitted -->
63937
64938
  `;
@@ -64812,7 +65813,11 @@ function addRows(construct, data, sheet) {
64812
65813
  let cellNode = escapeXml ``;
64813
65814
  // Either formula or static value inside the cell
64814
65815
  if (cell.isFormula) {
64815
- ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));
65816
+ const res = addFormula(cell);
65817
+ if (!res) {
65818
+ continue;
65819
+ }
65820
+ ({ attrs: additionalAttrs, node: cellNode } = res);
64816
65821
  }
64817
65822
  else if (cell.content && isMarkdownLink(cell.content)) {
64818
65823
  const { label } = parseMarkdownLink(cell.content);
@@ -65818,13 +66823,14 @@ const helpers = {
65818
66823
  UuidGenerator,
65819
66824
  formatValue,
65820
66825
  createCurrencyFormat,
66826
+ ColorGenerator,
65821
66827
  computeTextWidth,
65822
66828
  createEmptyWorkbookData,
65823
66829
  createEmptySheet,
65824
66830
  createEmptyExcelSheet,
65825
66831
  getDefaultChartJsRuntime,
65826
66832
  chartFontColor,
65827
- ChartColors,
66833
+ getChartAxisTitleRuntime,
65828
66834
  getFillingMode,
65829
66835
  rgbaToHex,
65830
66836
  colorToRGBA,
@@ -65881,9 +66887,10 @@ const components = {
65881
66887
  GridOverlay,
65882
66888
  ScorecardChart,
65883
66889
  LineConfigPanel,
65884
- GenericChartDesignPanel,
65885
66890
  BarConfigPanel,
66891
+ PieChartDesignPanel,
65886
66892
  GenericChartConfigPanel,
66893
+ ChartWithAxisDesignPanel,
65887
66894
  GaugeChartConfigPanel,
65888
66895
  GaugeChartDesignPanel,
65889
66896
  ScorecardChartConfigPanel,
@@ -65982,6 +66989,6 @@ exports.tokenColors = tokenColors;
65982
66989
  exports.tokenize = tokenize;
65983
66990
 
65984
66991
 
65985
- __info__.version = "17.3.0-alpha.9";
65986
- __info__.date = "2024-05-24T11:32:11.976Z";
65987
- __info__.hash = "aac246d";
66992
+ __info__.version = "17.3.1";
66993
+ __info__.date = "2024-06-03T15:28:03.284Z";
66994
+ __info__.hash = "605d098";