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