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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.3.0-alpha.9
7
- * @date 2024-05-24T11:32:11.976Z
8
- * @hash aac246d
6
+ * @version 17.3.0
7
+ * @date 2024-05-31T14:21:26.547Z
8
+ * @hash 271202e
9
9
  */
10
10
 
11
11
  (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);
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);
@@ -36711,14 +37465,15 @@ stores.inject(MyMetaStore, storeInstance);
36711
37465
  }
36712
37466
  onKeyDown(ev) {
36713
37467
  const figure = this.props.figure;
36714
- switch (ev.key) {
37468
+ const keyDownShortcut = keyboardEventToShortcutString(ev);
37469
+ switch (keyDownShortcut) {
36715
37470
  case "Delete":
37471
+ case "Backspace":
36716
37472
  this.env.model.dispatch("DELETE_FIGURE", {
36717
37473
  sheetId: this.env.model.getters.getActiveSheetId(),
36718
37474
  id: figure.id,
36719
37475
  });
36720
37476
  this.props.onFigureDeleted();
36721
- ev.stopPropagation();
36722
37477
  ev.preventDefault();
36723
37478
  ev.stopPropagation();
36724
37479
  break;
@@ -36739,7 +37494,22 @@ stores.inject(MyMetaStore, storeInstance);
36739
37494
  x: figure.x + delta[0],
36740
37495
  y: figure.y + delta[1],
36741
37496
  });
37497
+ ev.preventDefault();
36742
37498
  ev.stopPropagation();
37499
+ break;
37500
+ case "Ctrl+A":
37501
+ // Maybe in the future we will implement a way to select all figures
37502
+ ev.preventDefault();
37503
+ ev.stopPropagation();
37504
+ break;
37505
+ case "Ctrl+Y":
37506
+ case "Ctrl+Z":
37507
+ if (keyDownShortcut === "Ctrl+Y") {
37508
+ this.env.model.dispatch("REQUEST_REDO");
37509
+ }
37510
+ else if (keyDownShortcut === "Ctrl+Z") {
37511
+ this.env.model.dispatch("REQUEST_UNDO");
37512
+ }
36743
37513
  ev.preventDefault();
36744
37514
  ev.stopPropagation();
36745
37515
  break;
@@ -38019,9 +38789,6 @@ stores.inject(MyMetaStore, storeInstance);
38019
38789
 
38020
38790
  class FilterIconsOverlay extends owl.Component {
38021
38791
  static template = "o-spreadsheet-FilterIconsOverlay";
38022
- static props = {
38023
- onMouseDown: Function,
38024
- };
38025
38792
  static components = {
38026
38793
  GridCellIcon,
38027
38794
  FilterIcon,
@@ -38261,6 +39028,7 @@ stores.inject(MyMetaStore, storeInstance);
38261
39028
  };
38262
39029
  gridOverlay = owl.useRef("gridOverlay");
38263
39030
  gridOverlayRect = useAbsoluteBoundingRect(this.gridOverlay);
39031
+ cellPopovers;
38264
39032
  setup() {
38265
39033
  useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered);
38266
39034
  const resizeObserver = new ResizeObserver(() => {
@@ -38282,6 +39050,7 @@ stores.inject(MyMetaStore, storeInstance);
38282
39050
  const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
38283
39051
  return scrollY > 0;
38284
39052
  });
39053
+ this.cellPopovers = useStore(CellPopoverStore);
38285
39054
  }
38286
39055
  get gridOverlayEl() {
38287
39056
  if (!this.gridOverlay.el) {
@@ -38295,16 +39064,18 @@ stores.inject(MyMetaStore, storeInstance);
38295
39064
  get isPaintingFormat() {
38296
39065
  return this.env.model.getters.isPaintingFormat();
38297
39066
  }
38298
- onMouseDown(ev, modifiers) {
39067
+ onMouseDown(ev) {
38299
39068
  if (ev.button > 0) {
38300
39069
  // not main button, probably a context menu
38301
39070
  return;
38302
39071
  }
39072
+ if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
39073
+ this.cellPopovers.close();
39074
+ }
38303
39075
  const [col, row] = this.getCartesianCoordinates(ev);
38304
39076
  this.props.onCellClicked(col, row, {
38305
39077
  expandZone: ev.shiftKey,
38306
39078
  addZone: isCtrlKey(ev),
38307
- closePopover: modifiers?.closePopover ?? true,
38308
39079
  });
38309
39080
  }
38310
39081
  onDoubleClick(ev) {
@@ -40448,9 +41219,6 @@ stores.inject(MyMetaStore, storeInstance);
40448
41219
  // Zone selection with mouse
40449
41220
  // ---------------------------------------------------------------------------
40450
41221
  onCellClicked(col, row, modifiers) {
40451
- if (modifiers.closePopover && this.cellPopovers.isOpen) {
40452
- this.cellPopovers.close();
40453
- }
40454
41222
  if (this.composerStore.editionMode === "editing") {
40455
41223
  this.composerStore.stopEdition();
40456
41224
  }
@@ -42323,11 +43091,20 @@ stores.inject(MyMetaStore, storeInstance);
42323
43091
  return "imageSrc" in data;
42324
43092
  }
42325
43093
  function convertChartData(chartData) {
42326
- const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
43094
+ const dataSetsHaveTitle = chartData.dataSets.some((ds) => "reference" in (ds.label ?? {}));
42327
43095
  const labelRange = chartData.labelRange
42328
43096
  ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
42329
43097
  : undefined;
42330
- let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
43098
+ const dataSets = chartData.dataSets.map((data) => {
43099
+ let label = undefined;
43100
+ if (data.label && "text" in data.label) {
43101
+ label = data.label.text;
43102
+ }
43103
+ return {
43104
+ dataRange: convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle),
43105
+ label,
43106
+ };
43107
+ });
42331
43108
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
42332
43109
  if (chartData.type === "pie") {
42333
43110
  dataSets.reverse();
@@ -42336,10 +43113,9 @@ stores.inject(MyMetaStore, storeInstance);
42336
43113
  dataSets,
42337
43114
  dataSetsHaveTitle,
42338
43115
  labelRange,
42339
- title: chartData.title || "",
43116
+ title: chartData.title ?? { text: "" },
42340
43117
  type: chartData.type,
42341
43118
  background: convertColor({ rgb: chartData.backgroundColor }) || "#FFFFFF",
42342
- verticalAxisPosition: chartData.verticalAxisPosition,
42343
43119
  legendPosition: chartData.legendPosition,
42344
43120
  stacked: chartData.stacked || false,
42345
43121
  aggregated: false,
@@ -43454,25 +44230,20 @@ stores.inject(MyMetaStore, storeInstance);
43454
44230
  return this.extractComboChart(rootChartElement);
43455
44231
  }
43456
44232
  // 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) => {
44233
+ const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:chart > c:title a:t" }, (textElement) => {
43458
44234
  return textElement.textContent || "";
43459
44235
  }).join("");
43460
44236
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
43461
44237
  default: "clustered",
43462
44238
  }).asString();
43463
44239
  return {
43464
- title: chartTitle,
44240
+ title: { text: chartTitle },
43465
44241
  type: CHART_TYPE_CONVERSION_MAP[chartType],
43466
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
44242
+ dataSets: this.extractChartDatasets(this.querySelectorAll(rootChartElement, `c:${chartType}`), chartType),
43467
44243
  labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
43468
44244
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43469
44245
  default: "ffffff",
43470
44246
  }).asString(),
43471
- verticalAxisPosition: this.extractChildAttr(rootChartElement, "c:valAx > c:axPos", "val", {
43472
- default: "l",
43473
- }).asString() === "r"
43474
- ? "right"
43475
- : "left",
43476
44247
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
43477
44248
  default: "b",
43478
44249
  }).asString()],
@@ -43490,21 +44261,16 @@ stores.inject(MyMetaStore, storeInstance);
43490
44261
  default: "clustered",
43491
44262
  }).asString();
43492
44263
  return {
43493
- title: chartTitle,
44264
+ title: { text: chartTitle },
43494
44265
  type: "combo",
43495
44266
  dataSets: [
43496
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
43497
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
44267
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:barChart`), "comboChart"),
44268
+ ...this.extractChartDatasets(this.querySelectorAll(chartElement, `c:lineChart`), "comboChart"),
43498
44269
  ],
43499
44270
  labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
43500
44271
  backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
43501
44272
  default: "ffffff",
43502
44273
  }).asString(),
43503
- verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
43504
- default: "l",
43505
- }).asString() === "r"
43506
- ? "right"
43507
- : "left",
43508
44274
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
43509
44275
  default: "b",
43510
44276
  }).asString()],
@@ -43512,21 +44278,49 @@ stores.inject(MyMetaStore, storeInstance);
43512
44278
  fontColor: "000000",
43513
44279
  };
43514
44280
  }
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
- });
44281
+ extractChartDatasets(chartElements, chartType) {
44282
+ return Array.from(chartElements)
44283
+ .map((element) => {
44284
+ if (chartType === "scatterChart") {
44285
+ return this.extractScatterChartDatasets(element);
44286
+ }
44287
+ return this.mapOnElements({ parent: element, query: "c:ser" }, (chartDataElement) => {
44288
+ let label = {};
44289
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44290
+ if (reference) {
44291
+ label = { reference };
44292
+ }
44293
+ else {
44294
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44295
+ if (text) {
44296
+ label = { text };
44297
+ }
44298
+ }
44299
+ return {
44300
+ label,
44301
+ range: this.extractChildTextContent(chartDataElement, "c:val c:f", {
44302
+ required: true,
44303
+ }),
44304
+ };
44305
+ });
44306
+ })
44307
+ .flat();
43525
44308
  }
43526
44309
  extractScatterChartDatasets(chartElement) {
43527
44310
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
44311
+ let label = {};
44312
+ const reference = this.extractChildTextContent(chartDataElement, "c:tx c:f");
44313
+ if (reference) {
44314
+ label = { reference };
44315
+ }
44316
+ else {
44317
+ const text = this.extractChildTextContent(chartDataElement, "c:tx c:v");
44318
+ if (text) {
44319
+ label = { text };
44320
+ }
44321
+ }
43528
44322
  return {
43529
- label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
44323
+ label,
43530
44324
  range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
43531
44325
  };
43532
44326
  });
@@ -44258,7 +45052,7 @@ stores.inject(MyMetaStore, storeInstance);
44258
45052
  return relsFile;
44259
45053
  }
44260
45054
 
44261
- const EXCEL_IMPORT_VERSION = 12;
45055
+ const EXCEL_IMPORT_VERSION = 16;
44262
45056
  class XlsxReader {
44263
45057
  warningManager;
44264
45058
  xmls;
@@ -44736,6 +45530,30 @@ stores.inject(MyMetaStore, storeInstance);
44736
45530
  return data;
44737
45531
  },
44738
45532
  },
45533
+ {
45534
+ description: "transform chart data structure (2)",
45535
+ from: 16,
45536
+ to: 17,
45537
+ applyMigration(data) {
45538
+ for (const sheet of data.sheets || []) {
45539
+ for (const f in sheet.figures || []) {
45540
+ const figure = sheet.figures[f];
45541
+ if ("title" in figure.data && typeof figure.data.title === "string") {
45542
+ figure.data.title = { text: figure.data.title };
45543
+ }
45544
+ const figureType = figure.data.type;
45545
+ if (!["line", "bar", "pie", "scatter", "waterfall", "combo"].includes(figureType)) {
45546
+ continue;
45547
+ }
45548
+ const { dataSets, ...newData } = sheet.figures[f].data;
45549
+ const newDataSets = dataSets.map((dataRange) => ({ dataRange }));
45550
+ newData.dataSets = newDataSets;
45551
+ sheet.figures[f].data = newData;
45552
+ }
45553
+ }
45554
+ return data;
45555
+ },
45556
+ },
44739
45557
  ];
44740
45558
  /**
44741
45559
  * This function is used to repair faulty data independently of the migration.
@@ -48347,6 +49165,9 @@ stores.inject(MyMetaStore, storeInstance);
48347
49165
  if (range.invalidXc) {
48348
49166
  return range.invalidXc;
48349
49167
  }
49168
+ if (!this.getters.tryGetSheet(range.sheetId)) {
49169
+ return CellErrorType.InvalidReference;
49170
+ }
48350
49171
  if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {
48351
49172
  return CellErrorType.InvalidReference;
48352
49173
  }
@@ -51765,7 +52586,6 @@ stores.inject(MyMetaStore, storeInstance);
51765
52586
  *
51766
52587
  */
51767
52588
  class SpreadingRelation {
51768
- createEmptyPositionSet;
51769
52589
  /**
51770
52590
  * Internal structure:
51771
52591
  * For something like
@@ -51796,9 +52616,6 @@ stores.inject(MyMetaStore, storeInstance);
51796
52616
  */
51797
52617
  resultsToArrayFormulas = new PositionMap();
51798
52618
  arrayFormulasToResults = new PositionMap();
51799
- constructor(createEmptyPositionSet) {
51800
- this.createEmptyPositionSet = createEmptyPositionSet;
51801
- }
51802
52619
  getFormulaPositionsSpreadingOn(resultPosition) {
51803
52620
  return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
51804
52621
  }
@@ -51817,13 +52634,13 @@ stores.inject(MyMetaStore, storeInstance);
51817
52634
  */
51818
52635
  addRelation({ arrayFormulaPosition, resultPosition, }) {
51819
52636
  if (!this.resultsToArrayFormulas.has(resultPosition)) {
51820
- this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
52637
+ this.resultsToArrayFormulas.set(resultPosition, []);
51821
52638
  }
51822
- this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
52639
+ this.resultsToArrayFormulas.get(resultPosition)?.push(arrayFormulaPosition);
51823
52640
  if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
51824
- this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
52641
+ this.arrayFormulasToResults.set(arrayFormulaPosition, []);
51825
52642
  }
51826
- this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
52643
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.push(resultPosition);
51827
52644
  }
51828
52645
  hasArrayFormulaResult(position) {
51829
52646
  return this.resultsToArrayFormulas.has(position);
@@ -51844,7 +52661,7 @@ stores.inject(MyMetaStore, storeInstance);
51844
52661
  evaluatedCells = new PositionMap();
51845
52662
  formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
51846
52663
  blockedArrayFormulas = new PositionSet({});
51847
- spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52664
+ spreadingRelations = new SpreadingRelation();
51848
52665
  constructor(context, getters) {
51849
52666
  this.context = context;
51850
52667
  this.getters = getters;
@@ -51938,7 +52755,7 @@ stores.inject(MyMetaStore, storeInstance);
51938
52755
  }
51939
52756
  buildDependencyGraph() {
51940
52757
  this.blockedArrayFormulas = this.createEmptyPositionSet();
51941
- this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
52758
+ this.spreadingRelations = new SpreadingRelation();
51942
52759
  this.formulaDependencies = lazy(() => {
51943
52760
  const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
51944
52761
  .filter((range) => !range.invalidSheetName && !range.invalidXc)
@@ -52070,6 +52887,7 @@ stores.inject(MyMetaStore, storeInstance);
52070
52887
  const nbColumns = formulaReturn.length;
52071
52888
  const nbRows = formulaReturn[0].length;
52072
52889
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52890
+ this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52073
52891
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
52074
52892
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
52075
52893
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
@@ -52092,6 +52910,18 @@ stores.inject(MyMetaStore, storeInstance);
52092
52910
  }
52093
52911
  throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
52094
52912
  }
52913
+ assertNoMergedCellsInSpreadZone({ sheetId, col, row }, matrixResult) {
52914
+ const mergedCells = this.getters.getMergesInZone(sheetId, {
52915
+ top: row,
52916
+ bottom: row + matrixResult.length,
52917
+ left: col,
52918
+ right: col + matrixResult[0].length,
52919
+ });
52920
+ if (mergedCells.length === 0) {
52921
+ return;
52922
+ }
52923
+ throw new SplillBlockedError(_t("Merged cells found in the spill zone. Please unmerge cells before using array formulas."));
52924
+ }
52095
52925
  updateSpreadRelation({ sheetId, col, row, }) {
52096
52926
  const arrayFormulaPosition = { sheetId, col, row };
52097
52927
  return (i, j) => {
@@ -52329,10 +53159,6 @@ stores.inject(MyMetaStore, storeInstance);
52329
53159
  this.evaluator.updateDependencies(cmd);
52330
53160
  }
52331
53161
  break;
52332
- case "DUPLICATE_SHEET":
52333
- case "CREATE_SHEET":
52334
- this.shouldRebuildDependenciesGraph = true;
52335
- break;
52336
53162
  case "EVALUATE_CELLS":
52337
53163
  this.evaluator.evaluateAllCells();
52338
53164
  break;
@@ -52433,16 +53259,22 @@ stores.inject(MyMetaStore, storeInstance);
52433
53259
  let newContent = undefined;
52434
53260
  let newFormat = undefined;
52435
53261
  let isExported = true;
53262
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52436
53263
  const formulaCell = this.getCorrespondingFormulaCell(position);
52437
53264
  if (formulaCell) {
52438
53265
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
52439
53266
  isFormula = isExported;
52440
53267
  if (!isExported) {
52441
- newContent = (value ?? "").toString();
52442
- newFormat = evaluatedCell.format;
53268
+ // If the cell contains a non-exported formula and that is evaluates to
53269
+ // nothing* ,we don't export it.
53270
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
53271
+ // the empty string.
53272
+ if (value !== "") {
53273
+ newContent = (value ?? "").toString();
53274
+ newFormat = evaluatedCell.format;
53275
+ }
52443
53276
  }
52444
53277
  }
52445
- const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
52446
53278
  const exportedCellData = exportedSheetData.cells[xc] || {};
52447
53279
  const format = newFormat
52448
53280
  ? getItemId(newFormat, data.formats)
@@ -57065,7 +57897,7 @@ stores.inject(MyMetaStore, storeInstance);
57065
57897
  paintFormatStatus = "inactive";
57066
57898
  originSheetId;
57067
57899
  copiedData;
57068
- _isCutOperation;
57900
+ _isCutOperation = false;
57069
57901
  // ---------------------------------------------------------------------------
57070
57902
  // Command Handling
57071
57903
  // ---------------------------------------------------------------------------
@@ -57077,14 +57909,17 @@ stores.inject(MyMetaStore, storeInstance);
57077
57909
  case "PASTE_FROM_OS_CLIPBOARD": {
57078
57910
  const copiedData = this.convertOSClipboardData(cmd.text);
57079
57911
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57080
- return this.isPasteAllowed(cmd.target, copiedData, { pasteOption });
57912
+ return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
57081
57913
  }
57082
57914
  case "PASTE": {
57083
57915
  if (!this.copiedData) {
57084
57916
  return "EmptyClipboard" /* CommandResult.EmptyClipboard */;
57085
57917
  }
57086
57918
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57087
- return this.isPasteAllowed(cmd.target, this.copiedData, { pasteOption });
57919
+ return this.isPasteAllowed(cmd.target, this.copiedData, {
57920
+ pasteOption: pasteOption,
57921
+ isCutOperation: this._isCutOperation,
57922
+ });
57088
57923
  }
57089
57924
  case "COPY_PASTE_CELLS_ABOVE": {
57090
57925
  const zones = this.getters.getSelectedZones();
@@ -57102,13 +57937,13 @@ stores.inject(MyMetaStore, storeInstance);
57102
57937
  }
57103
57938
  case "INSERT_CELL": {
57104
57939
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57105
- const copiedData = this.copy("CUT", cut);
57106
- return this.isPasteAllowed(paste, copiedData, {});
57940
+ const copiedData = this.copy(cut);
57941
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57107
57942
  }
57108
57943
  case "DELETE_CELL": {
57109
57944
  const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
57110
- const copiedData = this.copy("CUT", cut);
57111
- return this.isPasteAllowed(paste, copiedData, {});
57945
+ const copiedData = this.copy(cut);
57946
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
57112
57947
  }
57113
57948
  case "ACTIVATE_PAINT_FORMAT": {
57114
57949
  if (this.paintFormatStatus !== "inactive") {
@@ -57126,23 +57961,27 @@ stores.inject(MyMetaStore, storeInstance);
57126
57961
  const zones = this.getters.getSelectedZones();
57127
57962
  this.status = "visible";
57128
57963
  this.originSheetId = this.getters.getActiveSheetId();
57129
- this.copiedData = this.copy(cmd.type, zones);
57964
+ this.copiedData = this.copy(zones);
57965
+ this._isCutOperation = cmd.type === "CUT";
57130
57966
  break;
57131
57967
  case "PASTE_FROM_OS_CLIPBOARD": {
57968
+ this._isCutOperation = false;
57132
57969
  this.copiedData = this.convertOSClipboardData(cmd.text);
57133
57970
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57134
- this.paste(cmd.target, {
57971
+ this.paste(cmd.target, this.copiedData, {
57135
57972
  pasteOption,
57136
57973
  selectTarget: true,
57974
+ isCutOperation: false,
57137
57975
  });
57138
57976
  this.status = "invisible";
57139
57977
  break;
57140
57978
  }
57141
57979
  case "PASTE": {
57142
57980
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
57143
- this.paste(cmd.target, {
57981
+ this.paste(cmd.target, this.copiedData, {
57144
57982
  pasteOption,
57145
57983
  selectTarget: true,
57984
+ isCutOperation: this._isCutOperation,
57146
57985
  });
57147
57986
  if (this.paintFormatStatus === "oneOff") {
57148
57987
  this.paintFormatStatus = "inactive";
@@ -57163,9 +58002,9 @@ stores.inject(MyMetaStore, storeInstance);
57163
58002
  top: multipleRowsInSelection ? zone.top : zone.top - 1,
57164
58003
  };
57165
58004
  this.originSheetId = this.getters.getActiveSheetId();
57166
- this.copiedData = this.copy("COPY", [copyTarget]);
57167
- this.paste([zone], {
57168
- pasteOption: undefined,
58005
+ const copiedData = this.copy([copyTarget]);
58006
+ this.paste([zone], copiedData, {
58007
+ isCutOperation: false,
57169
58008
  selectTarget: true,
57170
58009
  });
57171
58010
  }
@@ -57180,9 +58019,9 @@ stores.inject(MyMetaStore, storeInstance);
57180
58019
  left: multipleColsInSelection ? zone.left : zone.left - 1,
57181
58020
  };
57182
58021
  this.originSheetId = this.getters.getActiveSheetId();
57183
- this.copiedData = this.copy("COPY", [copyTarget]);
57184
- this.paste([zone], {
57185
- pasteOption: undefined,
58022
+ const copiedData = this.copy([copyTarget]);
58023
+ this.paste([zone], copiedData, {
58024
+ isCutOperation: false,
57186
58025
  selectTarget: true,
57187
58026
  });
57188
58027
  }
@@ -57198,14 +58037,14 @@ stores.inject(MyMetaStore, storeInstance);
57198
58037
  }
57199
58038
  break;
57200
58039
  }
57201
- this.copiedData = this.copy("CUT", cut);
57202
- this.paste(paste, {});
58040
+ const copiedData = this.copy(cut);
58041
+ this.paste(paste, copiedData, { isCutOperation: true });
57203
58042
  break;
57204
58043
  }
57205
58044
  case "INSERT_CELL": {
57206
58045
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
57207
- this.copiedData = this.copy("CUT", cut);
57208
- this.paste(paste, {});
58046
+ const copiedData = this.copy(cut);
58047
+ this.paste(paste, copiedData, { isCutOperation: true });
57209
58048
  break;
57210
58049
  }
57211
58050
  case "ADD_COLUMNS_ROWS": {
@@ -57237,7 +58076,8 @@ stores.inject(MyMetaStore, storeInstance);
57237
58076
  break;
57238
58077
  }
57239
58078
  case "REPEAT_PASTE": {
57240
- this.paste(cmd.target, {
58079
+ this.paste(cmd.target, this.copiedData, {
58080
+ isCutOperation: false,
57241
58081
  pasteOption: cmd.pasteOption,
57242
58082
  selectTarget: true,
57243
58083
  });
@@ -57245,7 +58085,7 @@ stores.inject(MyMetaStore, storeInstance);
57245
58085
  }
57246
58086
  case "ACTIVATE_PAINT_FORMAT": {
57247
58087
  const zones = this.getters.getSelectedZones();
57248
- this.copiedData = this.copy("COPY", zones);
58088
+ this.copiedData = this.copy(zones);
57249
58089
  this.status = "visible";
57250
58090
  if (cmd.persistent) {
57251
58091
  this.paintFormatStatus = "persistent";
@@ -57276,7 +58116,6 @@ stores.inject(MyMetaStore, storeInstance);
57276
58116
  }
57277
58117
  }
57278
58118
  convertOSClipboardData(clipboardData) {
57279
- this._isCutOperation = false;
57280
58119
  const handlers = clipboardHandlersRegistries.figureHandlers
57281
58120
  .getAll()
57282
58121
  .map((handler) => new handler(this.getters, this.dispatch));
@@ -57314,7 +58153,6 @@ stores.inject(MyMetaStore, storeInstance);
57314
58153
  for (const handler of this.selectClipboardHandlers(copiedData)) {
57315
58154
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
57316
58155
  ...options,
57317
- isCutOperation: this.isCutOperation(),
57318
58156
  });
57319
58157
  if (result !== "Success" /* CommandResult.Success */) {
57320
58158
  return result;
@@ -57337,9 +58175,8 @@ stores.inject(MyMetaStore, storeInstance);
57337
58175
  }
57338
58176
  return false;
57339
58177
  }
57340
- copy(operation, zones) {
58178
+ copy(zones) {
57341
58179
  let copiedData = {};
57342
- this._isCutOperation = operation === "CUT";
57343
58180
  const clipboardData = this.getClipboardData(zones);
57344
58181
  for (const handler of this.selectClipboardHandlers(clipboardData)) {
57345
58182
  const data = handler.copy(clipboardData);
@@ -57347,8 +58184,8 @@ stores.inject(MyMetaStore, storeInstance);
57347
58184
  }
57348
58185
  return copiedData;
57349
58186
  }
57350
- paste(zones, options) {
57351
- if (!this.copiedData) {
58187
+ paste(zones, copiedData, options) {
58188
+ if (!copiedData) {
57352
58189
  return;
57353
58190
  }
57354
58191
  let zone = undefined;
@@ -57356,12 +58193,9 @@ stores.inject(MyMetaStore, storeInstance);
57356
58193
  let target = {
57357
58194
  zones,
57358
58195
  };
57359
- const handlers = this.selectClipboardHandlers(this.copiedData);
58196
+ const handlers = this.selectClipboardHandlers(copiedData);
57360
58197
  for (const handler of handlers) {
57361
- const currentTarget = handler.getPasteTarget(zones, this.copiedData, {
57362
- ...options,
57363
- isCutOperation: this.isCutOperation(),
57364
- });
58198
+ const currentTarget = handler.getPasteTarget(zones, copiedData, options);
57365
58199
  if (currentTarget.figureId) {
57366
58200
  target.figureId = currentTarget.figureId;
57367
58201
  }
@@ -57377,7 +58211,7 @@ stores.inject(MyMetaStore, storeInstance);
57377
58211
  if (zone !== undefined) {
57378
58212
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
57379
58213
  }
57380
- handlers.forEach((handler) => handler.paste(target, this.copiedData, { ...options, isCutOperation: this.isCutOperation() }));
58214
+ handlers.forEach((handler) => handler.paste(target, copiedData, options));
57381
58215
  if (!options?.selectTarget) {
57382
58216
  return;
57383
58217
  }
@@ -59763,6 +60597,7 @@ stores.inject(MyMetaStore, storeInstance);
59763
60597
  sheetDivRef = owl.useRef("sheetDiv");
59764
60598
  sheetNameRef = owl.useRef("sheetNameSpan");
59765
60599
  editionState = "initializing";
60600
+ DOMFocusableElementStore;
59766
60601
  setup() {
59767
60602
  owl.onMounted(() => {
59768
60603
  if (this.isSheetActive) {
@@ -59775,6 +60610,7 @@ stores.inject(MyMetaStore, storeInstance);
59775
60610
  this.focusInputAndSelectContent();
59776
60611
  }
59777
60612
  });
60613
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
59778
60614
  }
59779
60615
  focusInputAndSelectContent() {
59780
60616
  if (!this.state.isEditing || !this.sheetNameRef.el)
@@ -59816,9 +60652,11 @@ stores.inject(MyMetaStore, storeInstance);
59816
60652
  if (ev.key === "Enter") {
59817
60653
  ev.preventDefault();
59818
60654
  this.stopEdition();
60655
+ this.DOMFocusableElementStore.focus();
59819
60656
  }
59820
60657
  if (ev.key === "Escape") {
59821
60658
  this.cancelEdition();
60659
+ this.DOMFocusableElementStore.focus();
59822
60660
  }
59823
60661
  }
59824
60662
  onClickSheetName(ev) {
@@ -61867,6 +62705,9 @@ stores.inject(MyMetaStore, storeInstance);
61867
62705
  static template = "o-spreadsheet-Spreadsheet";
61868
62706
  static props = {
61869
62707
  model: Object,
62708
+ notifyUser: { type: Function, optional: true },
62709
+ raiseError: { type: Function, optional: true },
62710
+ askConfirmation: { type: Function, optional: true },
61870
62711
  };
61871
62712
  static components = {
61872
62713
  TopBar,
@@ -61913,7 +62754,11 @@ stores.inject(MyMetaStore, storeInstance);
61913
62754
  toggleSidePanel: this.sidePanel.toggle.bind(this.sidePanel),
61914
62755
  clipboard: this.env.clipboard || instantiateClipboard(),
61915
62756
  startCellEdition: (content) => this.composerFocusStore.focusGridComposerCell(content),
62757
+ notifyUser: (notification) => this.notificationStore.notifyUser(notification),
62758
+ askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
62759
+ raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
61916
62760
  });
62761
+ this.notificationStore.updateNotificationCallbacks({ ...this.props });
61917
62762
  owl.useEffect(() => {
61918
62763
  /**
61919
62764
  * Only refocus the grid if the active element is not a child of the spreadsheet
@@ -61936,6 +62781,9 @@ stores.inject(MyMetaStore, storeInstance);
61936
62781
  if (nextProps.model !== this.props.model) {
61937
62782
  throw new Error("Changing the props model is not supported at the moment.");
61938
62783
  }
62784
+ if (!deepEquals(nextProps, this.props)) {
62785
+ this.notificationStore.updateNotificationCallbacks({ ...nextProps });
62786
+ }
61939
62787
  });
61940
62788
  const render = batched(this.render.bind(this, true));
61941
62789
  owl.onMounted(() => {
@@ -61953,7 +62801,7 @@ stores.inject(MyMetaStore, storeInstance);
61953
62801
  bindModelEvents() {
61954
62802
  this.model.on("update", this, () => this.render(true));
61955
62803
  this.model.on("notify-ui", this, (notification) => this.notificationStore.notifyUser(notification));
61956
- this.model.on("raise-error-ui", this, ({ text }) => this.env.raiseError(text));
62804
+ this.model.on("raise-error-ui", this, ({ text }) => this.notificationStore.raiseError(text));
61957
62805
  }
61958
62806
  unbindModelEvents() {
61959
62807
  this.model.off("update", this);
@@ -63508,10 +64356,13 @@ stores.inject(MyMetaStore, storeInstance);
63508
64356
  });
63509
64357
  // <manualLayout/> to manually position the chart in the figure container
63510
64358
  let title = escapeXml ``;
63511
- if (chart.data.title) {
64359
+ if (chart.data.title?.text) {
64360
+ const color = chart.data.title.color
64361
+ ? toXlsxHexColor(chart.data.title.color)
64362
+ : chart.data.fontColor;
63512
64363
  title = escapeXml /*xml*/ `
63513
64364
  <c:title>
63514
- ${insertText(chart.data.title, chart.data.fontColor)}
64365
+ ${insertText(chart.data.title.text, color, DEFAULT_CHART_FONT_SIZE, chart.data.title)}
63515
64366
  <c:overlay val="0" />
63516
64367
  </c:title>
63517
64368
  `;
@@ -63599,7 +64450,7 @@ stores.inject(MyMetaStore, storeInstance);
63599
64450
  </a:ln>
63600
64451
  `;
63601
64452
  }
63602
- function insertText(text, fontColor = "000000", fontsize = 22) {
64453
+ function insertText(text, fontColor = "000000", fontsize = DEFAULT_CHART_FONT_SIZE, style = {}) {
63603
64454
  return escapeXml /*xml*/ `
63604
64455
  <c:tx>
63605
64456
  <c:rich>
@@ -63607,13 +64458,13 @@ stores.inject(MyMetaStore, storeInstance);
63607
64458
  <a:lstStyle />
63608
64459
  <a:p>
63609
64460
  <a:pPr lvl="0">
63610
- <a:defRPr b="0">
64461
+ <a:defRPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}">
63611
64462
  ${solidFill(fontColor)}
63612
64463
  <a:latin typeface="+mn-lt"/>
63613
64464
  </a:defRPr>
63614
64465
  </a:pPr>
63615
64466
  <a:r> <!-- Runs -->
63616
- <a:rPr sz="${fontsize * 100}"/>
64467
+ <a:rPr b="${style?.bold ? 1 : 0}" i="${style?.italic ? 1 : 0}" sz="${fontsize * 100}"/>
63617
64468
  <a:t>${text}</a:t>
63618
64469
  </a:r>
63619
64470
  </a:p>
@@ -63642,6 +64493,24 @@ stores.inject(MyMetaStore, storeInstance);
63642
64493
  </c:txPr>
63643
64494
  `;
63644
64495
  }
64496
+ function extractDataSetLabel(label) {
64497
+ if (!label) {
64498
+ return escapeXml /*xml*/ ``;
64499
+ }
64500
+ if ("text" in label && label.text) {
64501
+ return escapeXml /*xml*/ `
64502
+ <c:tx><c:v>${label.text}</c:v></c:tx>
64503
+ `;
64504
+ }
64505
+ if ("reference" in label && label.reference) {
64506
+ return escapeXml /*xml*/ `
64507
+ <c:tx>
64508
+ ${stringRef(label.reference)}
64509
+ </c:tx>
64510
+ `;
64511
+ }
64512
+ return escapeXml /*xml*/ ``;
64513
+ }
63645
64514
  function addBarChart(chart) {
63646
64515
  // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
63647
64516
  // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
@@ -63649,46 +64518,72 @@ stores.inject(MyMetaStore, storeInstance);
63649
64518
  //
63650
64519
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63651
64520
  // 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 = [];
64521
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64522
+ const colors = new ColorGenerator(dataSetsColors);
64523
+ const leftDataSetsNodes = [];
64524
+ const rightDataSetsNodes = [];
63654
64525
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63655
64526
  const color = toXlsxHexColor(colors.next());
63656
64527
  const dataShapeProperty = shapeProperty({
63657
64528
  backgroundColor: color,
63658
64529
  line: { color },
63659
64530
  });
63660
- dataSetsNodes.push(escapeXml /*xml*/ `
64531
+ const dataSetNode = escapeXml /*xml*/ `
63661
64532
  <c:ser>
63662
64533
  <c:idx val="${dsIndex}"/>
63663
64534
  <c:order val="${dsIndex}"/>
63664
- ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64535
+ ${extractDataSetLabel(dataset.label)}
63665
64536
  ${dataShapeProperty}
63666
64537
  ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63667
64538
  <c:val> <!-- x-coordinate values -->
63668
64539
  ${numberRef(dataset.range)}
63669
64540
  </c:val>
63670
64541
  </c:ser>
63671
- `);
64542
+ `;
64543
+ if (dataset.rightYAxis) {
64544
+ rightDataSetsNodes.push(dataSetNode);
64545
+ }
64546
+ else {
64547
+ leftDataSetsNodes.push(dataSetNode);
64548
+ }
63672
64549
  }
63673
- // Excel does not support this feature
63674
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63675
64550
  const grouping = chart.stacked ? "stacked" : "clustered";
63676
64551
  const overlap = chart.stacked ? 100 : -20;
63677
64552
  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
- `;
64553
+ ${leftDataSetsNodes.length
64554
+ ? escapeXml /*xml*/ `
64555
+ <c:barChart>
64556
+ <c:barDir val="col"/>
64557
+ <c:grouping val="${grouping}"/>
64558
+ <c:overlap val="${overlap}"/>
64559
+ <c:gapWidth val="70"/>
64560
+ <!-- each data marker in the series does not have a different color -->
64561
+ <c:varyColors val="0"/>
64562
+ ${joinXmlNodes(leftDataSetsNodes)}
64563
+ <c:axId val="${catAxId}" />
64564
+ <c:axId val="${valAxId}" />
64565
+ </c:barChart>
64566
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64567
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64568
+ `
64569
+ : ""}
64570
+ ${rightDataSetsNodes.length
64571
+ ? escapeXml /*xml*/ `
64572
+ <c:barChart>
64573
+ <c:barDir val="col"/>
64574
+ <c:grouping val="${grouping}"/>
64575
+ <c:overlap val="${overlap}"/>
64576
+ <c:gapWidth val="70"/>
64577
+ <!-- each data marker in the series does not have a different color -->
64578
+ <c:varyColors val="0"/>
64579
+ ${joinXmlNodes(rightDataSetsNodes)}
64580
+ <c:axId val="${catAxId + 1}" />
64581
+ <c:axId val="${valAxId + 1}" />
64582
+ </c:barChart>
64583
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64584
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64585
+ `
64586
+ : ""}`;
63692
64587
  }
63693
64588
  function addComboChart(chart) {
63694
64589
  // 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 +64592,38 @@ stores.inject(MyMetaStore, storeInstance);
63697
64592
  //
63698
64593
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
63699
64594
  // 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)) {
64595
+ const dataSets = chart.dataSets;
64596
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
64597
+ const colors = new ColorGenerator(dataSetsColors);
64598
+ let dataSet = dataSets[0];
64599
+ const firstColor = toXlsxHexColor(colors.next());
64600
+ const useRightAxisForBarSerie = dataSet.rightYAxis ?? false;
64601
+ const barDataSetNode = escapeXml /*xml*/ `
64602
+ <c:ser>
64603
+ <c:idx val="0"/>
64604
+ <c:order val="0"/>
64605
+ ${extractDataSetLabel(dataSet.label)}
64606
+ ${shapeProperty({
64607
+ backgroundColor: firstColor,
64608
+ line: { color: firstColor },
64609
+ })}
64610
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64611
+ <!-- x-coordinate values -->
64612
+ <c:val>
64613
+ ${numberRef(dataSet.range)}
64614
+ </c:val>
64615
+ </c:ser>
64616
+ `;
64617
+ const leftDataSetsNodes = [];
64618
+ const rightDataSetsNodes = [];
64619
+ for (let dsIndex = 1; dsIndex < dataSets.length; dsIndex++) {
64620
+ dataSet = dataSets[dsIndex];
63703
64621
  const color = toXlsxHexColor(colors.next());
63704
64622
  const dataShapeProperty = shapeProperty({
63705
64623
  backgroundColor: color,
63706
64624
  line: { color },
63707
64625
  });
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*/ `
64626
+ const dataSetNode = escapeXml /*xml*/ `
63722
64627
  <c:ser>
63723
64628
  <c:idx val="${dsIndex}"/>
63724
64629
  <c:order val="${dsIndex}"/>
@@ -63726,18 +64631,24 @@ stores.inject(MyMetaStore, storeInstance);
63726
64631
  <c:marker>
63727
64632
  <c:symbol val="circle" />
63728
64633
  <c:size val="5"/>
64634
+ ${dataShapeProperty}
63729
64635
  </c:marker>
63730
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64636
+ ${extractDataSetLabel(dataSet.label)}
63731
64637
  ${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)}
64638
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
64639
+ <!-- x-coordinate values -->
64640
+ <c:val>
64641
+ ${numberRef(dataSet.range)}
63735
64642
  </c:val>
63736
64643
  </c:ser>
63737
- `);
64644
+ `;
64645
+ if (dataSet.rightYAxis) {
64646
+ rightDataSetsNodes.push(dataSetNode);
64647
+ }
64648
+ else {
64649
+ leftDataSetsNodes.push(dataSetNode);
64650
+ }
63738
64651
  }
63739
- // Excel does not support this feature
63740
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63741
64652
  const overlap = chart.stacked ? 100 : -20;
63742
64653
  return escapeXml /*xml*/ `
63743
64654
  <c:barChart>
@@ -63747,34 +64658,63 @@ stores.inject(MyMetaStore, storeInstance);
63747
64658
  <c:gapWidth val="70"/>
63748
64659
  <!-- each data marker in the series does not have a different color -->
63749
64660
  <c:varyColors val="0"/>
63750
- ${dataSetsNodes[0]}
63751
- <c:axId val="${catAxId}" />
63752
- <c:axId val="${valAxId}" />
64661
+ ${barDataSetNode}
64662
+ <c:axId val="${catAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
64663
+ <c:axId val="${valAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
63753
64664
  </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 })}
64665
+ ${leftDataSetsNodes.length
64666
+ ? escapeXml /*xml*/ `
64667
+ <c:lineChart>
64668
+ <c:grouping val="standard"/>
64669
+ <!-- each data marker in the series does not have a different color -->
64670
+ <c:varyColors val="0"/>
64671
+ ${joinXmlNodes(leftDataSetsNodes)}
64672
+ <c:axId val="${catAxId}" />
64673
+ <c:axId val="${valAxId}" />
64674
+ </c:lineChart>
64675
+ `
64676
+ : ""}
64677
+ ${rightDataSetsNodes.length
64678
+ ? escapeXml /*xml*/ `
64679
+ <c:lineChart>
64680
+ <c:grouping val="standard"/>
64681
+ <!-- each data marker in the series does not have a different color -->
64682
+ <c:varyColors val="0"/>
64683
+ ${joinXmlNodes(rightDataSetsNodes)}
64684
+ <c:axId val="${catAxId + 1}" />
64685
+ <c:axId val="${valAxId + 1}" />
64686
+ </c:lineChart>
64687
+ `
64688
+ : ""}
64689
+ ${!useRightAxisForBarSerie || leftDataSetsNodes.length
64690
+ ? escapeXml /*xml*/ `
64691
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64692
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64693
+ `
64694
+ : ""}
64695
+ ${useRightAxisForBarSerie || rightDataSetsNodes.length
64696
+ ? escapeXml /*xml*/ `
64697
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length || !useRightAxisForBarSerie ? 1 : 0)}
64698
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64699
+ `
64700
+ : ""}
63764
64701
  `;
63765
64702
  }
63766
64703
  function addLineChart(chart) {
63767
- const colors = new ChartColors();
63768
- const dataSetsNodes = [];
64704
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64705
+ const colors = new ColorGenerator(dataSetsColors);
64706
+ const leftDataSetsNodes = [];
64707
+ const rightDataSetsNodes = [];
63769
64708
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
64709
+ const color = toXlsxHexColor(colors.next());
63770
64710
  const dataShapeProperty = shapeProperty({
63771
64711
  line: {
63772
64712
  width: 2.5,
63773
64713
  style: "solid",
63774
- color: toXlsxHexColor(colors.next()),
64714
+ color,
63775
64715
  },
63776
64716
  });
63777
- dataSetsNodes.push(escapeXml /*xml*/ `
64717
+ const dataSetNode = escapeXml /*xml*/ `
63778
64718
  <c:ser>
63779
64719
  <c:idx val="${dsIndex}"/>
63780
64720
  <c:order val="${dsIndex}"/>
@@ -63782,37 +64722,63 @@ stores.inject(MyMetaStore, storeInstance);
63782
64722
  <c:marker>
63783
64723
  <c:symbol val="circle" />
63784
64724
  <c:size val="5"/>
64725
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63785
64726
  </c:marker>
63786
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64727
+ ${extractDataSetLabel(dataset.label)}
63787
64728
  ${dataShapeProperty}
63788
64729
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
63789
64730
  <c:val> <!-- x-coordinate values -->
63790
64731
  ${numberRef(dataset.range)}
63791
64732
  </c:val>
63792
64733
  </c:ser>
63793
- `);
64734
+ `;
64735
+ if (dataset.rightYAxis) {
64736
+ rightDataSetsNodes.push(dataSetNode);
64737
+ }
64738
+ else {
64739
+ leftDataSetsNodes.push(dataSetNode);
64740
+ }
63794
64741
  }
63795
- // Excel does not support this feature
63796
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63797
64742
  const grouping = chart.stacked ? "stacked" : "standard";
63798
64743
  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 })}
64744
+ ${leftDataSetsNodes.length
64745
+ ? escapeXml /*xml*/ `
64746
+ <c:lineChart>
64747
+ <c:grouping val="${grouping}"/>
64748
+ <!-- each data marker in the series does not have a different color -->
64749
+ <c:varyColors val="0"/>
64750
+ ${joinXmlNodes(leftDataSetsNodes)}
64751
+ <c:axId val="${catAxId}" />
64752
+ <c:axId val="${valAxId}" />
64753
+ </c:lineChart>
64754
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64755
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64756
+ `
64757
+ : ""}
64758
+ ${rightDataSetsNodes.length
64759
+ ? escapeXml /*xml*/ `
64760
+ <c:lineChart>
64761
+ <c:grouping val="${grouping}"/>
64762
+ <!-- each data marker in the series does not have a different color -->
64763
+ <c:varyColors val="0"/>
64764
+ ${joinXmlNodes(rightDataSetsNodes)}
64765
+ <c:axId val="${catAxId + 1}" />
64766
+ <c:axId val="${valAxId + 1}" />
64767
+ </c:lineChart>
64768
+ ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64769
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64770
+ `
64771
+ : ""}
63809
64772
  `;
63810
64773
  }
63811
64774
  function addScatterChart(chart) {
63812
- const colors = new ChartColors();
63813
- const dataSetsNodes = [];
64775
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
64776
+ const colors = new ColorGenerator(dataSetsColors);
64777
+ const leftDataSetsNodes = [];
64778
+ const rightDataSetsNodes = [];
63814
64779
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
63815
- dataSetsNodes.push(escapeXml /*xml*/ `
64780
+ const color = toXlsxHexColor(colors.next());
64781
+ const dataSetNode = escapeXml /*xml*/ `
63816
64782
  <c:ser>
63817
64783
  <c:idx val="${dsIndex}"/>
63818
64784
  <c:order val="${dsIndex}"/>
@@ -63827,8 +64793,9 @@ stores.inject(MyMetaStore, storeInstance);
63827
64793
  <c:marker>
63828
64794
  <c:symbol val="circle" />
63829
64795
  <c:size val="5"/>
63830
- ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
64796
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
63831
64797
  </c:marker>
64798
+ ${extractDataSetLabel(dataset.label)}
63832
64799
  ${chart.labelRange
63833
64800
  ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
63834
64801
  ${numberRef(chart.labelRange)}
@@ -63838,24 +64805,46 @@ stores.inject(MyMetaStore, storeInstance);
63838
64805
  ${numberRef(dataset.range)}
63839
64806
  </c:yVal>
63840
64807
  </c:ser>
63841
- `);
64808
+ `;
64809
+ if (dataset.rightYAxis) {
64810
+ rightDataSetsNodes.push(dataSetNode);
64811
+ }
64812
+ else {
64813
+ leftDataSetsNodes.push(dataSetNode);
64814
+ }
63842
64815
  }
63843
- const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
63844
64816
  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
- `;
64817
+ ${leftDataSetsNodes.length
64818
+ ? escapeXml /*xml*/ `
64819
+ <c:scatterChart>
64820
+ <!-- each data marker in the series does not have a different color -->
64821
+ <c:varyColors val="0"/>
64822
+ <c:scatterStyle val="lineMarker"/>
64823
+ ${joinXmlNodes(leftDataSetsNodes)}
64824
+ <c:axId val="${catAxId}" />
64825
+ <c:axId val="${valAxId}" />
64826
+ </c:scatterChart>
64827
+ ${addAx("b", "c:valAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
64828
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
64829
+ `
64830
+ : ""}
64831
+ ${rightDataSetsNodes.length
64832
+ ? escapeXml /*xml*/ `
64833
+ <c:scatterChart>
64834
+ <!-- each data marker in the series does not have a different color -->
64835
+ <c:varyColors val="0"/>
64836
+ <c:scatterStyle val="lineMarker"/>
64837
+ ${joinXmlNodes(rightDataSetsNodes)}
64838
+ <c:axId val="${catAxId + 1}" />
64839
+ <c:axId val="${valAxId + 1}" />
64840
+ </c:scatterChart>
64841
+ ${addAx("b", "c:valAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
64842
+ ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
64843
+ `
64844
+ : ""}`;
63856
64845
  }
63857
64846
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
63858
- const colors = new ChartColors();
64847
+ const colors = new ColorGenerator();
63859
64848
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
63860
64849
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
63861
64850
  const dataSetsNodes = [];
@@ -63879,7 +64868,7 @@ stores.inject(MyMetaStore, storeInstance);
63879
64868
  <c:ser>
63880
64869
  <c:idx val="${dsIndex}"/>
63881
64870
  <c:order val="${dsIndex}"/>
63882
- ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
64871
+ ${extractDataSetLabel(dataset.label)}
63883
64872
  ${joinXmlNodes(dataPoints)}
63884
64873
  ${insertDataLabels({ showLeaderLines: true })}
63885
64874
  ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
@@ -63911,14 +64900,16 @@ stores.inject(MyMetaStore, storeInstance);
63911
64900
  </dLbls>
63912
64901
  `;
63913
64902
  }
63914
- function addAx(position, axisName, axId, crossAxId, { fontColor }) {
64903
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
63915
64904
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
63916
64905
  // I.e. x-axis, will reference y-axis and vice-versa.
64906
+ const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
63917
64907
  return escapeXml /*xml*/ `
63918
64908
  <${axisName}>
63919
64909
  <c:axId val="${axId}"/>
63920
64910
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
63921
- <c:delete val="0"/> <!-- by default, axis are not displayed -->
64911
+ <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
64912
+ <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
63922
64913
  <c:scaling>
63923
64914
  <c:orientation val="minMax" />
63924
64915
  </c:scaling>
@@ -63928,9 +64919,9 @@ stores.inject(MyMetaStore, storeInstance);
63928
64919
  <c:minorTickMark val="none" />
63929
64920
  <c:numFmt formatCode="General" sourceLinked="1" />
63930
64921
  <c:title>
63931
- ${insertText("")}
64922
+ ${insertText(title?.text ?? "", color, 10, title)}
63932
64923
  </c:title>
63933
- ${insertTextProperties(10, fontColor)}
64924
+ ${insertTextProperties(10, defaultFontColor)}
63934
64925
  </${axisName}>
63935
64926
  <!-- <tickLblPos/> omitted -->
63936
64927
  `;
@@ -64811,7 +65802,11 @@ stores.inject(MyMetaStore, storeInstance);
64811
65802
  let cellNode = escapeXml ``;
64812
65803
  // Either formula or static value inside the cell
64813
65804
  if (cell.isFormula) {
64814
- ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));
65805
+ const res = addFormula(cell);
65806
+ if (!res) {
65807
+ continue;
65808
+ }
65809
+ ({ attrs: additionalAttrs, node: cellNode } = res);
64815
65810
  }
64816
65811
  else if (cell.content && isMarkdownLink(cell.content)) {
64817
65812
  const { label } = parseMarkdownLink(cell.content);
@@ -65817,13 +66812,14 @@ stores.inject(MyMetaStore, storeInstance);
65817
66812
  UuidGenerator,
65818
66813
  formatValue,
65819
66814
  createCurrencyFormat,
66815
+ ColorGenerator,
65820
66816
  computeTextWidth,
65821
66817
  createEmptyWorkbookData,
65822
66818
  createEmptySheet,
65823
66819
  createEmptyExcelSheet,
65824
66820
  getDefaultChartJsRuntime,
65825
66821
  chartFontColor,
65826
- ChartColors,
66822
+ getChartAxisTitleRuntime,
65827
66823
  getFillingMode,
65828
66824
  rgbaToHex,
65829
66825
  colorToRGBA,
@@ -65880,9 +66876,10 @@ stores.inject(MyMetaStore, storeInstance);
65880
66876
  GridOverlay,
65881
66877
  ScorecardChart,
65882
66878
  LineConfigPanel,
65883
- GenericChartDesignPanel,
65884
66879
  BarConfigPanel,
66880
+ PieChartDesignPanel,
65885
66881
  GenericChartConfigPanel,
66882
+ ChartWithAxisDesignPanel,
65886
66883
  GaugeChartConfigPanel,
65887
66884
  GaugeChartDesignPanel,
65888
66885
  ScorecardChartConfigPanel,
@@ -65981,9 +66978,9 @@ stores.inject(MyMetaStore, storeInstance);
65981
66978
  exports.tokenize = tokenize;
65982
66979
 
65983
66980
 
65984
- __info__.version = "17.3.0-alpha.9";
65985
- __info__.date = "2024-05-24T11:32:11.976Z";
65986
- __info__.hash = "aac246d";
66981
+ __info__.version = "17.3.0";
66982
+ __info__.date = "2024-05-31T14:21:26.547Z";
66983
+ __info__.hash = "271202e";
65987
66984
 
65988
66985
 
65989
66986
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);