@odoo/o-spreadsheet 18.5.0-alpha.6 → 18.5.0-alpha.8

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.5.0-alpha.6
6
- * @date 2025-08-05T06:27:49.505Z
7
- * @hash 67e091f
5
+ * @version 18.5.0-alpha.8
6
+ * @date 2025-08-18T08:17:58.775Z
7
+ * @hash 994f1cb
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -199,6 +199,7 @@
199
199
  const CHART_PADDING_TOP = 15;
200
200
  const CHART_TITLE_FONT_SIZE = 16;
201
201
  const CHART_AXIS_TITLE_FONT_SIZE = 12;
202
+ const MASTER_CHART_HEIGHT = 60;
202
203
  const SCORECARD_CHART_TITLE_FONT_SIZE = 14;
203
204
  const PIVOT_TOKEN_COLOR = "#F28C28";
204
205
  // Color picker defaults as upper case HEX to match `toHex`helper
@@ -414,6 +415,7 @@
414
415
  };
415
416
  const PIVOT_INDENT = 15;
416
417
  const PIVOT_COLLAPSE_ICON_SIZE = 12;
418
+ const PIVOT_MAX_NUMBER_OF_CELLS = 1e5;
417
419
  const DEFAULT_CURRENCY = {
418
420
  symbol: "$",
419
421
  position: "before",
@@ -2656,6 +2658,7 @@
2656
2658
  "SET_FORMULA_VISIBILITY",
2657
2659
  "UPDATE_FILTER",
2658
2660
  "UPDATE_CHART",
2661
+ "UPDATE_CAROUSEL_ACTIVE_ITEM",
2659
2662
  ]);
2660
2663
  const coreTypes = new Set([
2661
2664
  /** CELLS */
@@ -2698,6 +2701,8 @@
2698
2701
  "CREATE_FIGURE",
2699
2702
  "DELETE_FIGURE",
2700
2703
  "UPDATE_FIGURE",
2704
+ "CREATE_CAROUSEL",
2705
+ "UPDATE_CAROUSEL",
2701
2706
  /** FORMATTING */
2702
2707
  "SET_FORMATTING",
2703
2708
  "CLEAR_FORMATTING",
@@ -2707,6 +2712,7 @@
2707
2712
  /** CHART */
2708
2713
  "CREATE_CHART",
2709
2714
  "UPDATE_CHART",
2715
+ "DELETE_CHART",
2710
2716
  /** FILTERS */
2711
2717
  "CREATE_TABLE",
2712
2718
  "REMOVE_TABLE",
@@ -2901,6 +2907,7 @@
2901
2907
  CommandResult["InvalidColor"] = "InvalidColor";
2902
2908
  CommandResult["InvalidPivotDataSet"] = "InvalidPivotDataSet";
2903
2909
  CommandResult["InvalidPivotCustomField"] = "InvalidPivotCustomField";
2910
+ CommandResult["MissingFigureArguments"] = "MissingFigureArguments";
2904
2911
  })(exports.CommandResult || (exports.CommandResult = {}));
2905
2912
 
2906
2913
  const availableConditionalFormatOperators = new Set([
@@ -9221,7 +9228,7 @@
9221
9228
  return;
9222
9229
  }
9223
9230
  const copiedFigure = { ...figure };
9224
- const chart = this.getters.getChart(data.figureId);
9231
+ const chart = this.getters.getChartFromFigureId(data.figureId);
9225
9232
  if (!chart) {
9226
9233
  throw new Error(`No chart for the given id: ${data.figureId}`);
9227
9234
  }
@@ -9257,6 +9264,7 @@
9257
9264
  }
9258
9265
  this.dispatch("CREATE_CHART", {
9259
9266
  figureId,
9267
+ chartId: figureId,
9260
9268
  sheetId,
9261
9269
  definition: copy.getDefinition(),
9262
9270
  col,
@@ -19029,6 +19037,211 @@ stores.inject(MyMetaStore, storeInstance);
19029
19037
  XOR: XOR
19030
19038
  });
19031
19039
 
19040
+ const CfTerms = {
19041
+ Errors: {
19042
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
19043
+ ["FirstArgMissing" /* CommandResult.FirstArgMissing */]: _t("The argument is missing. Please provide a value"),
19044
+ ["SecondArgMissing" /* CommandResult.SecondArgMissing */]: _t("The second argument is missing. Please provide a value"),
19045
+ ["MinNaN" /* CommandResult.MinNaN */]: _t("The minpoint must be a number"),
19046
+ ["MidNaN" /* CommandResult.MidNaN */]: _t("The midpoint must be a number"),
19047
+ ["MaxNaN" /* CommandResult.MaxNaN */]: _t("The maxpoint must be a number"),
19048
+ ["ValueUpperInflectionNaN" /* CommandResult.ValueUpperInflectionNaN */]: _t("The first value must be a number"),
19049
+ ["ValueLowerInflectionNaN" /* CommandResult.ValueLowerInflectionNaN */]: _t("The second value must be a number"),
19050
+ ["MinBiggerThanMax" /* CommandResult.MinBiggerThanMax */]: _t("Minimum must be smaller then Maximum"),
19051
+ ["MinBiggerThanMid" /* CommandResult.MinBiggerThanMid */]: _t("Minimum must be smaller then Midpoint"),
19052
+ ["MidBiggerThanMax" /* CommandResult.MidBiggerThanMax */]: _t("Midpoint must be smaller then Maximum"),
19053
+ ["LowerBiggerThanUpper" /* CommandResult.LowerBiggerThanUpper */]: _t("Lower inflection point must be smaller than upper inflection point"),
19054
+ ["MinInvalidFormula" /* CommandResult.MinInvalidFormula */]: _t("Invalid Minpoint formula"),
19055
+ ["MaxInvalidFormula" /* CommandResult.MaxInvalidFormula */]: _t("Invalid Maxpoint formula"),
19056
+ ["MidInvalidFormula" /* CommandResult.MidInvalidFormula */]: _t("Invalid Midpoint formula"),
19057
+ ["ValueUpperInvalidFormula" /* CommandResult.ValueUpperInvalidFormula */]: _t("Invalid upper inflection point formula"),
19058
+ ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
19059
+ ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
19060
+ ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
19061
+ Unexpected: _t("The rule is invalid for an unknown reason"),
19062
+ },
19063
+ ColorScale: _t("Color scale"),
19064
+ IconSet: _t("Icon set"),
19065
+ DataBar: _t("Data bar"),
19066
+ };
19067
+ const ChartTerms = {
19068
+ Series: _t("Series"),
19069
+ BackgroundColor: _t("Background color"),
19070
+ StackedBarChart: _t("Stacked bar chart"),
19071
+ StackedLineChart: _t("Stacked line chart"),
19072
+ StackedAreaChart: _t("Stacked area chart"),
19073
+ StackedColumnChart: _t("Stacked column chart"),
19074
+ CumulativeData: _t("Cumulative data"),
19075
+ TreatLabelsAsText: _t("Treat labels as text"),
19076
+ AggregatedChart: _t("Aggregate"),
19077
+ Errors: {
19078
+ Unexpected: _t("The chart definition is invalid for an unknown reason"),
19079
+ // BASIC CHART ERRORS (LINE | BAR | PIE)
19080
+ ["InvalidDataSet" /* CommandResult.InvalidDataSet */]: _t("The dataset is invalid"),
19081
+ ["InvalidLabelRange" /* CommandResult.InvalidLabelRange */]: _t("Labels are invalid"),
19082
+ // SCORECARD CHART ERRORS
19083
+ ["InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */]: _t("The key value is invalid"),
19084
+ ["InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */]: _t("The baseline value is invalid"),
19085
+ // GAUGE CHART ERRORS
19086
+ ["InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */]: _t("The data range is invalid"),
19087
+ ["EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */]: _t("A minimum range limit value is needed"),
19088
+ ["GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */]: _t("The minimum range limit value must be a number"),
19089
+ ["EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */]: _t("A maximum range limit value is needed"),
19090
+ ["GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */]: _t("The maximum range limit value must be a number"),
19091
+ ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
19092
+ ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
19093
+ },
19094
+ GeoChart: {
19095
+ ColorScales: {
19096
+ blues: _t("Blues"),
19097
+ cividis: _t("Cividis"),
19098
+ greens: _t("Greens"),
19099
+ greys: _t("Greys"),
19100
+ oranges: _t("Oranges"),
19101
+ purples: _t("Purples"),
19102
+ rainbow: _t("Rainbow"),
19103
+ reds: _t("Reds"),
19104
+ viridis: _t("Viridis"),
19105
+ },
19106
+ },
19107
+ };
19108
+ const CustomCurrencyTerms = {
19109
+ Custom: _t("Custom"),
19110
+ };
19111
+ const MergeErrorMessage = _t("Merged cells are preventing this operation. Unmerge those cells and try again.");
19112
+ const SplitToColumnsTerms = {
19113
+ Errors: {
19114
+ Unexpected: _t("Cannot split the selection for an unknown reason"),
19115
+ ["NoSplitSeparatorInSelection" /* CommandResult.NoSplitSeparatorInSelection */]: _t("There is no match for the selected separator in the selection"),
19116
+ ["MoreThanOneColumnSelected" /* CommandResult.MoreThanOneColumnSelected */]: _t("Only a selection from a single column can be split"),
19117
+ ["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]: _t("Splitting will overwrite existing content"),
19118
+ },
19119
+ };
19120
+ const RemoveDuplicateTerms = {
19121
+ Errors: {
19122
+ Unexpected: _t("Cannot remove duplicates for an unknown reason"),
19123
+ ["MoreThanOneRangeSelected" /* CommandResult.MoreThanOneRangeSelected */]: _t("Please select only one range of cells"),
19124
+ ["EmptyTarget" /* CommandResult.EmptyTarget */]: _t("Please select a range of cells containing values."),
19125
+ ["NoColumnsProvided" /* CommandResult.NoColumnsProvided */]: _t("Please select at latest one column to analyze."),
19126
+ //TODO: Remove it when accept to copy and paste merge cells
19127
+ ["WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */]: _t("This operation is not possible due to a merge. Please remove the merges first than try again."),
19128
+ },
19129
+ };
19130
+ const DVTerms = {
19131
+ DateIs: {
19132
+ today: _t("today"),
19133
+ yesterday: _t("yesterday"),
19134
+ tomorrow: _t("tomorrow"),
19135
+ lastWeek: _t("in the past week"),
19136
+ lastMonth: _t("in the past month"),
19137
+ lastYear: _t("in the past year"),
19138
+ },
19139
+ DateIsBefore: {
19140
+ today: _t("today"),
19141
+ yesterday: _t("yesterday"),
19142
+ tomorrow: _t("tomorrow"),
19143
+ lastWeek: _t("one week ago"),
19144
+ lastMonth: _t("one month ago"),
19145
+ lastYear: _t("one year ago"),
19146
+ },
19147
+ CriterionError: {
19148
+ notEmptyValue: _t("The value must not be empty"),
19149
+ numberValue: _t("The value must be a number"),
19150
+ dateValue: _t("The value must be a date"),
19151
+ validRange: _t("The value must be a valid range"),
19152
+ validFormula: _t("The formula must be valid"),
19153
+ },
19154
+ Errors: {
19155
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
19156
+ ["InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */]: _t("One or more of the provided criteria values are invalid. Please review and correct them."),
19157
+ ["InvalidNumberOfCriterionValues" /* CommandResult.InvalidNumberOfCriterionValues */]: _t("One or more of the provided criteria values are missing."),
19158
+ Unexpected: _t("The rule is invalid for an unknown reason."),
19159
+ },
19160
+ };
19161
+ const TableTerms = {
19162
+ Errors: {
19163
+ Unexpected: _t("The table zone is invalid for an unknown reason"),
19164
+ ["TableOverlap" /* CommandResult.TableOverlap */]: _t("You cannot create overlapping tables."),
19165
+ ["NonContinuousTargets" /* CommandResult.NonContinuousTargets */]: _t("A table can only be created on a continuous selection."),
19166
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
19167
+ ["TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */]: _t("The range is out of the sheet"),
19168
+ },
19169
+ Checkboxes: {
19170
+ hasFilters: _t("Filter button"),
19171
+ headerRow: _t("Header row(s)"),
19172
+ bandedRows: _t("Banded rows"),
19173
+ firstColumn: _t("First column"),
19174
+ lastColumn: _t("Last column"),
19175
+ bandedColumns: _t("Banded columns"),
19176
+ automaticAutofill: _t("Automatically autofill formulas"),
19177
+ totalRow: _t("Total row"),
19178
+ isDynamic: _t("Auto-adjust to formula result"),
19179
+ },
19180
+ Tooltips: {
19181
+ filterWithoutHeader: _t("Cannot have filters without a header row"),
19182
+ isDynamic: _t("For tables based on array formulas only"),
19183
+ },
19184
+ };
19185
+ const measureDisplayTerms = {
19186
+ labels: {
19187
+ no_calculations: _t("No calculations"),
19188
+ "%_of_grand_total": _t("% of grand total"),
19189
+ "%_of_col_total": _t("% of column total"),
19190
+ "%_of_row_total": _t("% of row total"),
19191
+ "%_of": _t("% of"),
19192
+ "%_of_parent_row_total": _t("% of parent row total"),
19193
+ "%_of_parent_col_total": _t("% of parent column total"),
19194
+ "%_of_parent_total": _t("% of parent total"),
19195
+ difference_from: _t("Difference from"),
19196
+ "%_difference_from": _t("% difference from"),
19197
+ running_total: _t("Running total"),
19198
+ "%_running_total": _t("% Running total"),
19199
+ rank_asc: _t("Rank smallest to largest"),
19200
+ rank_desc: _t("Rank largest to smallest"),
19201
+ index: _t("Index"),
19202
+ },
19203
+ descriptions: {
19204
+ "%_of_grand_total": () => _t("Displayed as % of grand total"),
19205
+ "%_of_col_total": () => _t("Displayed as % of column total"),
19206
+ "%_of_row_total": () => _t("Displayed as % of row total"),
19207
+ "%_of": (field) => _t('Displayed as % of "%s"', field),
19208
+ "%_of_parent_row_total": (field) => _t('Displayed as % of parent row total of "%s"', field),
19209
+ "%_of_parent_col_total": () => _t("Displayed as % of parent column total"),
19210
+ "%_of_parent_total": (field) => _t('Displayed as % of parent "%s" total', field),
19211
+ difference_from: (field) => _t('Displayed as difference from "%s"', field),
19212
+ "%_difference_from": (field) => _t('Displayed as % difference from "%s"', field),
19213
+ running_total: (field) => _t('Displayed as running total based on "%s"', field),
19214
+ "%_running_total": (field) => _t('Displayed as % running total based on "%s"', field),
19215
+ rank_asc: (field) => _t('Displayed as rank from smallest to largest based on "%s"', field),
19216
+ rank_desc: (field) => _t('Displayed as rank largest to smallest based on "%s"', field),
19217
+ index: () => _t("Displayed as index"),
19218
+ },
19219
+ documentation: {
19220
+ no_calculations: _t("Displays the value that is entered in the field."),
19221
+ "%_of_grand_total": _t("Displays values as a percentage of the grand total of all the values or data points in the report."),
19222
+ "%_of_col_total": _t("Displays all the values in each column or series as a percentage of the total for the column or series."),
19223
+ "%_of_row_total": _t("Displays the value in each row or category as a percentage of the total for the row or category."),
19224
+ "%_of": _t("Displays values as a percentage of the value of the Base item in the Base field."),
19225
+ "%_of_parent_row_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on rows)"),
19226
+ "%_of_parent_col_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on columns)"),
19227
+ "%_of_parent_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item of the selected Base field)"),
19228
+ difference_from: _t("Displays values as the difference from the value of the Base item in the Base field."),
19229
+ "%_difference_from": _t("Displays values as the percentage difference from the value of the Base item in the Base field."),
19230
+ running_total: _t("Displays the value for successive items in the Base field as a running total."),
19231
+ "%_running_total": _t("Calculates the value as a percentage for successive items in the Base field that are displayed as a running total."),
19232
+ rank_asc: _t("Displays the rank of selected values in a specific field, listing the smallest item in the field as 1, and each larger value with a higher rank value."),
19233
+ rank_desc: _t("Displays the rank of selected values in a specific field, listing the largest item in the field as 1, and each smaller value with a higher rank value."),
19234
+ index: _t("Calculates values as follows:\n((value in cell) x (Grand Total of Grand Totals)) / ((Grand Row Total) x (Grand Column Total))"),
19235
+ },
19236
+ };
19237
+ function getPivotTooBigErrorMessage(numberOfCells, locale) {
19238
+ const formattedNumber = formatValue(numberOfCells, {
19239
+ format: "0,00",
19240
+ locale: locale,
19241
+ });
19242
+ return _t("Oops—this pivot table is quite large (%s cells). Try simplifying it using the side panel.", formattedNumber);
19243
+ }
19244
+
19032
19245
  /**
19033
19246
  * Get the pivot ID from the formula pivot ID.
19034
19247
  */
@@ -19639,6 +19852,9 @@ stores.inject(MyMetaStore, storeInstance);
19639
19852
  return error;
19640
19853
  }
19641
19854
  const table = pivot.getCollapsedTableStructure();
19855
+ if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
19856
+ return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
19857
+ }
19642
19858
  const cells = table.getPivotCells(visibilityOptions);
19643
19859
  let headerRows = 0;
19644
19860
  if (visibilityOptions.displayColumnHeaders) {
@@ -19737,7 +19953,7 @@ stores.inject(MyMetaStore, storeInstance);
19737
19953
  right: startingCol + offsetWidth - 1,
19738
19954
  bottom: startingRow + offsetHeight - 1,
19739
19955
  };
19740
- const range = this.getters.getRangeFromZone(this.__originSheetId, dependencyZone);
19956
+ const range = this.getters.getRangeFromZone(sheetId, dependencyZone);
19741
19957
  if (range.invalidXc || range.invalidSheetName) {
19742
19958
  return new InvalidReferenceError();
19743
19959
  }
@@ -22020,12 +22236,12 @@ stores.inject(MyMetaStore, storeInstance);
22020
22236
  }
22021
22237
  return pieColors;
22022
22238
  }
22023
- function truncateLabel(label) {
22239
+ function truncateLabel(label, maxLen = MAX_CHAR_LABEL) {
22024
22240
  if (!label) {
22025
22241
  return "";
22026
22242
  }
22027
- if (label.length > MAX_CHAR_LABEL) {
22028
- return label.substring(0, MAX_CHAR_LABEL) + "…";
22243
+ if (label.length > maxLen) {
22244
+ return label.substring(0, maxLen) + "…";
22029
22245
  }
22030
22246
  return label;
22031
22247
  }
@@ -22997,7 +23213,7 @@ stores.inject(MyMetaStore, storeInstance);
22997
23213
  class ChartJsComponent extends owl.Component {
22998
23214
  static template = "o-spreadsheet-ChartJsComponent";
22999
23215
  static props = {
23000
- figureUI: Object,
23216
+ chartId: String,
23001
23217
  isFullScreen: { type: Boolean, optional: true },
23002
23218
  };
23003
23219
  canvas = owl.useRef("graphContainer");
@@ -23012,7 +23228,7 @@ stores.inject(MyMetaStore, storeInstance);
23012
23228
  return `background-color: ${this.background}`;
23013
23229
  }
23014
23230
  get chartRuntime() {
23015
- const runtime = this.env.model.getters.getChartRuntime(this.props.figureUI.id);
23231
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
23016
23232
  if (!("chartJsConfig" in runtime)) {
23017
23233
  throw new Error("Unsupported chart runtime");
23018
23234
  }
@@ -23027,30 +23243,37 @@ stores.inject(MyMetaStore, storeInstance);
23027
23243
  const runtime = this.chartRuntime;
23028
23244
  this.currentRuntime = runtime;
23029
23245
  // Note: chartJS modify the runtime in place, so it's important to give it a copy
23030
- this.createChart(deepCopy(runtime.chartJsConfig));
23246
+ this.createChart(deepCopy(runtime));
23031
23247
  });
23032
- owl.onWillUnmount(() => this.chart?.destroy());
23248
+ owl.onWillUnmount(this.unmount.bind(this));
23033
23249
  owl.useEffect(() => {
23034
23250
  const runtime = this.chartRuntime;
23035
23251
  if (runtime !== this.currentRuntime) {
23036
23252
  if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
23037
23253
  this.chart?.destroy();
23038
- this.createChart(deepCopy(runtime.chartJsConfig));
23254
+ this.createChart(deepCopy(runtime));
23039
23255
  }
23040
23256
  else {
23041
- this.updateChartJs(deepCopy(runtime.chartJsConfig));
23257
+ this.updateChartJs(deepCopy(runtime));
23042
23258
  }
23043
23259
  this.currentRuntime = runtime;
23044
23260
  }
23045
23261
  else if (this.currentDevicePixelRatio !== window.devicePixelRatio) {
23046
23262
  this.currentDevicePixelRatio = window.devicePixelRatio;
23047
- this.updateChartJs(deepCopy(this.currentRuntime.chartJsConfig));
23263
+ this.updateChartJs(deepCopy(this.currentRuntime));
23048
23264
  }
23049
23265
  });
23050
23266
  }
23051
- createChart(chartData) {
23052
- if (this.env.model.getters.isDashboard() && this.animationStore) {
23053
- const chartType = this.env.model.getters.getChart(this.props.figureUI.id)?.type;
23267
+ unmount() {
23268
+ this.chart?.destroy();
23269
+ }
23270
+ get shouldAnimate() {
23271
+ return this.env.model.getters.isDashboard();
23272
+ }
23273
+ createChart(chartRuntime) {
23274
+ let chartData = chartRuntime.chartJsConfig;
23275
+ if (this.shouldAnimate && this.animationStore) {
23276
+ const chartType = this.env.model.getters.getChart(this.props.chartId)?.type;
23054
23277
  if (chartType && this.animationStore.animationPlayed[this.animationFigureId] !== chartType) {
23055
23278
  chartData = this.enableAnimationInChartData(chartData);
23056
23279
  this.animationStore.disableAnimationForChart(this.animationFigureId, chartType);
@@ -23060,9 +23283,10 @@ stores.inject(MyMetaStore, storeInstance);
23060
23283
  const ctx = canvas.getContext("2d");
23061
23284
  this.chart = new window.Chart(ctx, chartData);
23062
23285
  }
23063
- updateChartJs(chartData) {
23064
- if (this.env.model.getters.isDashboard()) {
23065
- const chartType = this.env.model.getters.getChart(this.props.figureUI.id)?.type;
23286
+ updateChartJs(chartRuntime) {
23287
+ let chartData = chartRuntime.chartJsConfig;
23288
+ if (this.shouldAnimate) {
23289
+ const chartType = this.env.model.getters.getChart(this.props.chartId)?.type;
23066
23290
  if (chartType && this.hasChartDataChanged() && this.animationStore) {
23067
23291
  chartData = this.enableAnimationInChartData(chartData);
23068
23292
  this.animationStore.disableAnimationForChart(this.animationFigureId, chartType);
@@ -23090,9 +23314,7 @@ stores.inject(MyMetaStore, storeInstance);
23090
23314
  };
23091
23315
  }
23092
23316
  get animationFigureId() {
23093
- return this.props.isFullScreen
23094
- ? this.props.figureUI.id + "-fullscreen"
23095
- : this.props.figureUI.id;
23317
+ return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
23096
23318
  }
23097
23319
  }
23098
23320
 
@@ -23757,14 +23979,14 @@ stores.inject(MyMetaStore, storeInstance);
23757
23979
  class ScorecardChart extends owl.Component {
23758
23980
  static template = "o-spreadsheet-ScorecardChart";
23759
23981
  static props = {
23760
- figureUI: Object,
23982
+ chartId: String,
23761
23983
  };
23762
23984
  canvas = owl.useRef("chartContainer");
23763
23985
  get runtime() {
23764
- return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
23986
+ return this.env.model.getters.getChartRuntime(this.props.chartId);
23765
23987
  }
23766
23988
  get title() {
23767
- const title = this.env.model.getters.getChartDefinition(this.props.figureUI.id).title.text ?? "";
23989
+ const title = this.env.model.getters.getChartDefinition(this.props.chartId).title.text ?? "";
23768
23990
  // chart titles are extracted from .json files and they are translated at runtime here
23769
23991
  return _t(title);
23770
23992
  }
@@ -24470,204 +24692,6 @@ stores.inject(MyMetaStore, storeInstance);
24470
24692
  },
24471
24693
  };
24472
24694
 
24473
- const CfTerms = {
24474
- Errors: {
24475
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
24476
- ["FirstArgMissing" /* CommandResult.FirstArgMissing */]: _t("The argument is missing. Please provide a value"),
24477
- ["SecondArgMissing" /* CommandResult.SecondArgMissing */]: _t("The second argument is missing. Please provide a value"),
24478
- ["MinNaN" /* CommandResult.MinNaN */]: _t("The minpoint must be a number"),
24479
- ["MidNaN" /* CommandResult.MidNaN */]: _t("The midpoint must be a number"),
24480
- ["MaxNaN" /* CommandResult.MaxNaN */]: _t("The maxpoint must be a number"),
24481
- ["ValueUpperInflectionNaN" /* CommandResult.ValueUpperInflectionNaN */]: _t("The first value must be a number"),
24482
- ["ValueLowerInflectionNaN" /* CommandResult.ValueLowerInflectionNaN */]: _t("The second value must be a number"),
24483
- ["MinBiggerThanMax" /* CommandResult.MinBiggerThanMax */]: _t("Minimum must be smaller then Maximum"),
24484
- ["MinBiggerThanMid" /* CommandResult.MinBiggerThanMid */]: _t("Minimum must be smaller then Midpoint"),
24485
- ["MidBiggerThanMax" /* CommandResult.MidBiggerThanMax */]: _t("Midpoint must be smaller then Maximum"),
24486
- ["LowerBiggerThanUpper" /* CommandResult.LowerBiggerThanUpper */]: _t("Lower inflection point must be smaller than upper inflection point"),
24487
- ["MinInvalidFormula" /* CommandResult.MinInvalidFormula */]: _t("Invalid Minpoint formula"),
24488
- ["MaxInvalidFormula" /* CommandResult.MaxInvalidFormula */]: _t("Invalid Maxpoint formula"),
24489
- ["MidInvalidFormula" /* CommandResult.MidInvalidFormula */]: _t("Invalid Midpoint formula"),
24490
- ["ValueUpperInvalidFormula" /* CommandResult.ValueUpperInvalidFormula */]: _t("Invalid upper inflection point formula"),
24491
- ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
24492
- ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
24493
- ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
24494
- Unexpected: _t("The rule is invalid for an unknown reason"),
24495
- },
24496
- ColorScale: _t("Color scale"),
24497
- IconSet: _t("Icon set"),
24498
- DataBar: _t("Data bar"),
24499
- };
24500
- const ChartTerms = {
24501
- Series: _t("Series"),
24502
- BackgroundColor: _t("Background color"),
24503
- StackedBarChart: _t("Stacked bar chart"),
24504
- StackedLineChart: _t("Stacked line chart"),
24505
- StackedAreaChart: _t("Stacked area chart"),
24506
- StackedColumnChart: _t("Stacked column chart"),
24507
- CumulativeData: _t("Cumulative data"),
24508
- TreatLabelsAsText: _t("Treat labels as text"),
24509
- AggregatedChart: _t("Aggregate"),
24510
- Errors: {
24511
- Unexpected: _t("The chart definition is invalid for an unknown reason"),
24512
- // BASIC CHART ERRORS (LINE | BAR | PIE)
24513
- ["InvalidDataSet" /* CommandResult.InvalidDataSet */]: _t("The dataset is invalid"),
24514
- ["InvalidLabelRange" /* CommandResult.InvalidLabelRange */]: _t("Labels are invalid"),
24515
- // SCORECARD CHART ERRORS
24516
- ["InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */]: _t("The key value is invalid"),
24517
- ["InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */]: _t("The baseline value is invalid"),
24518
- // GAUGE CHART ERRORS
24519
- ["InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */]: _t("The data range is invalid"),
24520
- ["EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */]: _t("A minimum range limit value is needed"),
24521
- ["GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */]: _t("The minimum range limit value must be a number"),
24522
- ["EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */]: _t("A maximum range limit value is needed"),
24523
- ["GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */]: _t("The maximum range limit value must be a number"),
24524
- ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
24525
- ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
24526
- },
24527
- GeoChart: {
24528
- ColorScales: {
24529
- blues: _t("Blues"),
24530
- cividis: _t("Cividis"),
24531
- greens: _t("Greens"),
24532
- greys: _t("Greys"),
24533
- oranges: _t("Oranges"),
24534
- purples: _t("Purples"),
24535
- rainbow: _t("Rainbow"),
24536
- reds: _t("Reds"),
24537
- viridis: _t("Viridis"),
24538
- },
24539
- },
24540
- };
24541
- const CustomCurrencyTerms = {
24542
- Custom: _t("Custom"),
24543
- };
24544
- const MergeErrorMessage = _t("Merged cells are preventing this operation. Unmerge those cells and try again.");
24545
- const SplitToColumnsTerms = {
24546
- Errors: {
24547
- Unexpected: _t("Cannot split the selection for an unknown reason"),
24548
- ["NoSplitSeparatorInSelection" /* CommandResult.NoSplitSeparatorInSelection */]: _t("There is no match for the selected separator in the selection"),
24549
- ["MoreThanOneColumnSelected" /* CommandResult.MoreThanOneColumnSelected */]: _t("Only a selection from a single column can be split"),
24550
- ["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]: _t("Splitting will overwrite existing content"),
24551
- },
24552
- };
24553
- const RemoveDuplicateTerms = {
24554
- Errors: {
24555
- Unexpected: _t("Cannot remove duplicates for an unknown reason"),
24556
- ["MoreThanOneRangeSelected" /* CommandResult.MoreThanOneRangeSelected */]: _t("Please select only one range of cells"),
24557
- ["EmptyTarget" /* CommandResult.EmptyTarget */]: _t("Please select a range of cells containing values."),
24558
- ["NoColumnsProvided" /* CommandResult.NoColumnsProvided */]: _t("Please select at latest one column to analyze."),
24559
- //TODO: Remove it when accept to copy and paste merge cells
24560
- ["WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */]: _t("This operation is not possible due to a merge. Please remove the merges first than try again."),
24561
- },
24562
- };
24563
- const DVTerms = {
24564
- DateIs: {
24565
- today: _t("today"),
24566
- yesterday: _t("yesterday"),
24567
- tomorrow: _t("tomorrow"),
24568
- lastWeek: _t("in the past week"),
24569
- lastMonth: _t("in the past month"),
24570
- lastYear: _t("in the past year"),
24571
- },
24572
- DateIsBefore: {
24573
- today: _t("today"),
24574
- yesterday: _t("yesterday"),
24575
- tomorrow: _t("tomorrow"),
24576
- lastWeek: _t("one week ago"),
24577
- lastMonth: _t("one month ago"),
24578
- lastYear: _t("one year ago"),
24579
- },
24580
- CriterionError: {
24581
- notEmptyValue: _t("The value must not be empty"),
24582
- numberValue: _t("The value must be a number"),
24583
- dateValue: _t("The value must be a date"),
24584
- validRange: _t("The value must be a valid range"),
24585
- validFormula: _t("The formula must be valid"),
24586
- },
24587
- Errors: {
24588
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
24589
- ["InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */]: _t("One or more of the provided criteria values are invalid. Please review and correct them."),
24590
- ["InvalidNumberOfCriterionValues" /* CommandResult.InvalidNumberOfCriterionValues */]: _t("One or more of the provided criteria values are missing."),
24591
- Unexpected: _t("The rule is invalid for an unknown reason."),
24592
- },
24593
- };
24594
- const TableTerms = {
24595
- Errors: {
24596
- Unexpected: _t("The table zone is invalid for an unknown reason"),
24597
- ["TableOverlap" /* CommandResult.TableOverlap */]: _t("You cannot create overlapping tables."),
24598
- ["NonContinuousTargets" /* CommandResult.NonContinuousTargets */]: _t("A table can only be created on a continuous selection."),
24599
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
24600
- ["TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */]: _t("The range is out of the sheet"),
24601
- },
24602
- Checkboxes: {
24603
- hasFilters: _t("Filter button"),
24604
- headerRow: _t("Header row(s)"),
24605
- bandedRows: _t("Banded rows"),
24606
- firstColumn: _t("First column"),
24607
- lastColumn: _t("Last column"),
24608
- bandedColumns: _t("Banded columns"),
24609
- automaticAutofill: _t("Automatically autofill formulas"),
24610
- totalRow: _t("Total row"),
24611
- isDynamic: _t("Auto-adjust to formula result"),
24612
- },
24613
- Tooltips: {
24614
- filterWithoutHeader: _t("Cannot have filters without a header row"),
24615
- isDynamic: _t("For tables based on array formulas only"),
24616
- },
24617
- };
24618
- const measureDisplayTerms = {
24619
- labels: {
24620
- no_calculations: _t("No calculations"),
24621
- "%_of_grand_total": _t("% of grand total"),
24622
- "%_of_col_total": _t("% of column total"),
24623
- "%_of_row_total": _t("% of row total"),
24624
- "%_of": _t("% of"),
24625
- "%_of_parent_row_total": _t("% of parent row total"),
24626
- "%_of_parent_col_total": _t("% of parent column total"),
24627
- "%_of_parent_total": _t("% of parent total"),
24628
- difference_from: _t("Difference from"),
24629
- "%_difference_from": _t("% difference from"),
24630
- running_total: _t("Running total"),
24631
- "%_running_total": _t("% Running total"),
24632
- rank_asc: _t("Rank smallest to largest"),
24633
- rank_desc: _t("Rank largest to smallest"),
24634
- index: _t("Index"),
24635
- },
24636
- descriptions: {
24637
- "%_of_grand_total": () => _t("Displayed as % of grand total"),
24638
- "%_of_col_total": () => _t("Displayed as % of column total"),
24639
- "%_of_row_total": () => _t("Displayed as % of row total"),
24640
- "%_of": (field) => _t('Displayed as % of "%s"', field),
24641
- "%_of_parent_row_total": (field) => _t('Displayed as % of parent row total of "%s"', field),
24642
- "%_of_parent_col_total": () => _t("Displayed as % of parent column total"),
24643
- "%_of_parent_total": (field) => _t('Displayed as % of parent "%s" total', field),
24644
- difference_from: (field) => _t('Displayed as difference from "%s"', field),
24645
- "%_difference_from": (field) => _t('Displayed as % difference from "%s"', field),
24646
- running_total: (field) => _t('Displayed as running total based on "%s"', field),
24647
- "%_running_total": (field) => _t('Displayed as % running total based on "%s"', field),
24648
- rank_asc: (field) => _t('Displayed as rank from smallest to largest based on "%s"', field),
24649
- rank_desc: (field) => _t('Displayed as rank largest to smallest based on "%s"', field),
24650
- index: () => _t("Displayed as index"),
24651
- },
24652
- documentation: {
24653
- no_calculations: _t("Displays the value that is entered in the field."),
24654
- "%_of_grand_total": _t("Displays values as a percentage of the grand total of all the values or data points in the report."),
24655
- "%_of_col_total": _t("Displays all the values in each column or series as a percentage of the total for the column or series."),
24656
- "%_of_row_total": _t("Displays the value in each row or category as a percentage of the total for the row or category."),
24657
- "%_of": _t("Displays values as a percentage of the value of the Base item in the Base field."),
24658
- "%_of_parent_row_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on rows)"),
24659
- "%_of_parent_col_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on columns)"),
24660
- "%_of_parent_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item of the selected Base field)"),
24661
- difference_from: _t("Displays values as the difference from the value of the Base item in the Base field."),
24662
- "%_difference_from": _t("Displays values as the percentage difference from the value of the Base item in the Base field."),
24663
- running_total: _t("Displays the value for successive items in the Base field as a running total."),
24664
- "%_running_total": _t("Calculates the value as a percentage for successive items in the Base field that are displayed as a running total."),
24665
- rank_asc: _t("Displays the rank of selected values in a specific field, listing the smallest item in the field as 1, and each larger value with a higher rank value."),
24666
- rank_desc: _t("Displays the rank of selected values in a specific field, listing the largest item in the field as 1, and each smaller value with a higher rank value."),
24667
- index: _t("Calculates values as follows:\n((value in cell) x (Grand Total of Grand Totals)) / ((Grand Row Total) x (Grand Column Total))"),
24668
- },
24669
- };
24670
-
24671
24695
  const UNIT_LENGTH = {
24672
24696
  second: 1000,
24673
24697
  minute: 1000 * 60,
@@ -26016,7 +26040,7 @@ stores.inject(MyMetaStore, storeInstance);
26016
26040
  ticks: {
26017
26041
  padding: 5,
26018
26042
  color: fontColor,
26019
- callback: function (tickValue) {
26043
+ callback: function (tickValue, index, ticks) {
26020
26044
  // Category axis callback's internal tick value is the index of the label
26021
26045
  // https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
26022
26046
  return truncateLabel(this.getLabelForValue(tickValue));
@@ -26588,6 +26612,7 @@ stores.inject(MyMetaStore, storeInstance);
26588
26612
  axesDesign;
26589
26613
  horizontal;
26590
26614
  showValues;
26615
+ zoomable;
26591
26616
  constructor(definition, sheetId, getters) {
26592
26617
  super(definition, sheetId, getters);
26593
26618
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -26601,6 +26626,7 @@ stores.inject(MyMetaStore, storeInstance);
26601
26626
  this.axesDesign = definition.axesDesign;
26602
26627
  this.horizontal = definition.horizontal;
26603
26628
  this.showValues = definition.showValues;
26629
+ this.zoomable = definition.zoomable;
26604
26630
  }
26605
26631
  static transformDefinition(chartSheetId, definition, applyChange) {
26606
26632
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -26622,6 +26648,7 @@ stores.inject(MyMetaStore, storeInstance);
26622
26648
  axesDesign: context.axesDesign,
26623
26649
  showValues: context.showValues,
26624
26650
  horizontal: context.horizontal,
26651
+ zoomable: context.zoomable,
26625
26652
  };
26626
26653
  }
26627
26654
  getContextCreation() {
@@ -26676,6 +26703,7 @@ stores.inject(MyMetaStore, storeInstance);
26676
26703
  axesDesign: this.axesDesign,
26677
26704
  horizontal: this.horizontal,
26678
26705
  showValues: this.showValues,
26706
+ zoomable: this.horizontal ? undefined : this.zoomable,
26679
26707
  };
26680
26708
  }
26681
26709
  getDefinitionForExcel() {
@@ -26724,6 +26752,621 @@ stores.inject(MyMetaStore, storeInstance);
26724
26752
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
26725
26753
  }
26726
26754
 
26755
+ class FullScreenChartStore extends SpreadsheetStore {
26756
+ mutators = ["toggleFullScreenChart"];
26757
+ fullScreenFigure = undefined;
26758
+ toggleFullScreenChart(figureId) {
26759
+ if (this.fullScreenFigure?.id === figureId) {
26760
+ this.fullScreenFigure = undefined;
26761
+ }
26762
+ else {
26763
+ this.makeFullScreen(figureId);
26764
+ }
26765
+ }
26766
+ makeFullScreen(figureId) {
26767
+ const sheetId = this.getters.getActiveSheetId();
26768
+ const figure = this.getters.getFigure(sheetId, figureId);
26769
+ if (figure) {
26770
+ this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
26771
+ }
26772
+ }
26773
+ }
26774
+
26775
+ const TREND_LINE_AXES_IDS = [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID];
26776
+ const ZOOMABLE_AXIS_IDS = ["x", ...TREND_LINE_AXES_IDS];
26777
+ class ZoomableChartStore extends SpreadsheetStore {
26778
+ mutators = [
26779
+ "resetAxisLimits",
26780
+ "updateAxisLimits",
26781
+ "updateTrendLineConfiguration",
26782
+ "clearAxisLimits",
26783
+ ];
26784
+ originalAxisLimits = {};
26785
+ currentAxesLimits = {};
26786
+ idConversion = {};
26787
+ handle(cmd) {
26788
+ switch (cmd.type) {
26789
+ case "DELETE_FIGURE":
26790
+ if (cmd.figureId && this.idConversion[cmd.figureId]) {
26791
+ for (const chartId of this.idConversion[cmd.figureId]) {
26792
+ delete this.originalAxisLimits[chartId];
26793
+ delete this.currentAxesLimits[chartId];
26794
+ }
26795
+ }
26796
+ break;
26797
+ case "UPDATE_CHART":
26798
+ const type = cmd.definition.type;
26799
+ const chartId = `${type}-${cmd.figureId}`;
26800
+ if (!this.idConversion[cmd.figureId]) {
26801
+ this.idConversion[cmd.figureId] = new Set();
26802
+ }
26803
+ this.idConversion[cmd.figureId].add(chartId);
26804
+ if (!("zoomable" in cmd.definition && cmd.definition.zoomable)) {
26805
+ this.clearAxisLimits(chartId);
26806
+ }
26807
+ break;
26808
+ }
26809
+ }
26810
+ clearAxisLimits(chartId) {
26811
+ delete this.originalAxisLimits[chartId];
26812
+ delete this.currentAxesLimits[chartId];
26813
+ return "noStateChange";
26814
+ }
26815
+ resetAxisLimits(chartId, limits) {
26816
+ for (const axisId of ZOOMABLE_AXIS_IDS) {
26817
+ if (limits?.[axisId]) {
26818
+ if (!this.originalAxisLimits[chartId]?.[axisId]) {
26819
+ this.originalAxisLimits[chartId] = {
26820
+ ...this.originalAxisLimits[chartId],
26821
+ [axisId]: {},
26822
+ };
26823
+ }
26824
+ this.originalAxisLimits[chartId][axisId]["min"] = limits[axisId].min;
26825
+ this.originalAxisLimits[chartId][axisId]["max"] = limits[axisId].max;
26826
+ }
26827
+ else {
26828
+ if (this.originalAxisLimits[chartId]?.[axisId]) {
26829
+ delete this.originalAxisLimits[chartId][axisId];
26830
+ }
26831
+ }
26832
+ }
26833
+ return "noStateChange";
26834
+ }
26835
+ updateAxisLimits(chartId, limits) {
26836
+ if (limits === undefined) {
26837
+ delete this.currentAxesLimits[chartId];
26838
+ return "noStateChange";
26839
+ }
26840
+ let { min, max } = limits;
26841
+ if (min > max) {
26842
+ [min, max] = [max, min];
26843
+ }
26844
+ this.currentAxesLimits[chartId] = { x: { min, max } };
26845
+ return "noStateChange";
26846
+ }
26847
+ /* Update the trend line axis configuration based on the current axis limits.
26848
+ * This function calculates the new limits for the trend line axes based on the current x-axis
26849
+ * limits and the original limits of the trend line axes.
26850
+ * It assumes that the origininal trend line axes are linear transformations of the original x-axis
26851
+ * limits and applies the same transformation to the current x-axis limits to get the new limits
26852
+ * for the current trend line axes.
26853
+ */
26854
+ updateTrendLineConfiguration(chartId) {
26855
+ if (!this.originalAxisLimits[chartId]) {
26856
+ return "noStateChange";
26857
+ }
26858
+ const chartLimits = this.originalAxisLimits[chartId].x;
26859
+ if (chartLimits === undefined) {
26860
+ return "noStateChange";
26861
+ }
26862
+ for (const axisId of TREND_LINE_AXES_IDS) {
26863
+ if (!this.originalAxisLimits[chartId][axisId]) {
26864
+ continue;
26865
+ }
26866
+ if (!this.currentAxesLimits[chartId]?.[axisId]) {
26867
+ this.currentAxesLimits[chartId] = {
26868
+ ...this.currentAxesLimits[chartId],
26869
+ [axisId]: {},
26870
+ };
26871
+ }
26872
+ if (this.currentAxesLimits[chartId]?.x === undefined) {
26873
+ return "noStateChange";
26874
+ }
26875
+ const realRange = chartLimits.max - chartLimits.min;
26876
+ const trendingLimits = this.originalAxisLimits[chartId][axisId];
26877
+ const trendingRange = trendingLimits.max - trendingLimits.min;
26878
+ const slope = trendingRange / realRange;
26879
+ const intercept = trendingLimits.min - chartLimits.min * slope;
26880
+ const newXMin = this.currentAxesLimits[chartId].x.min;
26881
+ const newXMax = this.currentAxesLimits[chartId].x.max;
26882
+ this.currentAxesLimits[chartId][axisId].min = newXMin * slope + intercept;
26883
+ this.currentAxesLimits[chartId][axisId].max = newXMax * slope + intercept;
26884
+ }
26885
+ return "noStateChange";
26886
+ }
26887
+ }
26888
+
26889
+ const zoomWindowPlugin = {
26890
+ id: "zoomWindowPlugin",
26891
+ afterDatasetsDraw: function (chart, args, options) {
26892
+ if (!options.getLowerBound || !options.getUpperBound) {
26893
+ return;
26894
+ }
26895
+ const { ctx, chartArea: { left, right, top, bottom }, } = chart;
26896
+ let lowerBound = options.getLowerBound() ?? left;
26897
+ let upperBound = options.getUpperBound() ?? right;
26898
+ if (lowerBound > upperBound) {
26899
+ [lowerBound, upperBound] = [upperBound, lowerBound];
26900
+ }
26901
+ lowerBound = Math.max(left, lowerBound);
26902
+ upperBound = Math.min(right, upperBound);
26903
+ if (lowerBound === left) {
26904
+ lowerBound -= 1;
26905
+ }
26906
+ if (upperBound === right) {
26907
+ upperBound += 1;
26908
+ }
26909
+ ctx.save();
26910
+ ctx.fillStyle = "rgba(255,255,255,0.5)";
26911
+ ctx.beginPath();
26912
+ ctx.rect(left, bottom, lowerBound - left, top - bottom);
26913
+ ctx.rect(upperBound, bottom, right - upperBound, top - bottom);
26914
+ ctx.fill();
26915
+ ctx.beginPath();
26916
+ ctx.strokeStyle = "#bbb";
26917
+ ctx.rect(lowerBound, bottom, upperBound - lowerBound, top - bottom);
26918
+ ctx.stroke();
26919
+ ctx.lineWidth = 2;
26920
+ ctx.beginPath();
26921
+ ctx.moveTo(lowerBound - 3, (top + bottom) / 2 - 7);
26922
+ ctx.lineTo(lowerBound - 3, (top + bottom) / 2 + 7);
26923
+ ctx.stroke();
26924
+ ctx.beginPath();
26925
+ ctx.moveTo(upperBound + 3, (top + bottom) / 2 - 7);
26926
+ ctx.lineTo(upperBound + 3, (top + bottom) / 2 + 7);
26927
+ ctx.stroke();
26928
+ ctx.restore();
26929
+ },
26930
+ };
26931
+
26932
+ css /* scss */ `
26933
+ .o-spreadsheet {
26934
+ .o-master-chart-container {
26935
+ height: ${MASTER_CHART_HEIGHT}px;
26936
+ }
26937
+ }
26938
+ `;
26939
+ chartJsExtensionRegistry.add("zoomWindowPlugin", {
26940
+ register: (Chart) => Chart.register(zoomWindowPlugin),
26941
+ unregister: (Chart) => Chart.unregister(zoomWindowPlugin),
26942
+ });
26943
+ class ZoomableChartJsComponent extends ChartJsComponent {
26944
+ static template = "o-spreadsheet-ZoomableChartJsComponent";
26945
+ store;
26946
+ fullScreenChartStore;
26947
+ masterChartCanvas = owl.useRef("masterChartCanvas");
26948
+ masterChart;
26949
+ mode;
26950
+ hasLinearScale;
26951
+ isBarChart;
26952
+ chartId = "";
26953
+ datasetBoundaries = { xMin: 0, xMax: 0 };
26954
+ removeEventListeners = () => { };
26955
+ setup() {
26956
+ this.store = useStore(ZoomableChartStore);
26957
+ this.fullScreenChartStore = useStore(FullScreenChartStore);
26958
+ super.setup();
26959
+ }
26960
+ unmount() {
26961
+ super.unmount();
26962
+ this.masterChart?.destroy();
26963
+ this.removeEventListeners();
26964
+ }
26965
+ get containerStyle() {
26966
+ const height = this.sliceable ? `calc(100% - ${MASTER_CHART_HEIGHT}px)` : "100%";
26967
+ return `
26968
+ height:${height};
26969
+ `;
26970
+ }
26971
+ get sliceable() {
26972
+ if (this.env.isDashboard()) {
26973
+ const fullScreenFigureId = this.fullScreenChartStore.fullScreenFigure?.id;
26974
+ const chartFigureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
26975
+ if (fullScreenFigureId === chartFigureId) {
26976
+ return true;
26977
+ }
26978
+ }
26979
+ const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
26980
+ return ("zoomable" in definition && definition?.zoomable) ?? false;
26981
+ }
26982
+ get axisOffset() {
26983
+ return !this.hasLinearScale && this.isBarChart ? 0.5 : 0;
26984
+ }
26985
+ getMasterChartConfiguration(chartData) {
26986
+ const config = chartData;
26987
+ return {
26988
+ ...config,
26989
+ options: {
26990
+ ...config.options,
26991
+ plugins: {
26992
+ ...config.options.plugins,
26993
+ zoomWindowPlugin: {
26994
+ getLowerBound: () => this.lowerBound,
26995
+ getUpperBound: () => this.upperBound,
26996
+ },
26997
+ },
26998
+ },
26999
+ };
27000
+ }
27001
+ getDetailChartConfiguration(chartData) {
27002
+ if (!this.sliceable) {
27003
+ return chartData;
27004
+ }
27005
+ const xAxis = this.store.currentAxesLimits[this.chartId]?.x;
27006
+ const xScale = {
27007
+ ...chartData.options.scales?.x,
27008
+ };
27009
+ if (xAxis?.min !== undefined) {
27010
+ xScale.min = this.hasLinearScale ? xAxis.min : Math.ceil(xAxis.min) - this.axisOffset;
27011
+ }
27012
+ if (xAxis?.max !== undefined) {
27013
+ xScale.max = this.hasLinearScale ? xAxis.max : Math.floor(xAxis.max) - this.axisOffset;
27014
+ }
27015
+ return {
27016
+ ...chartData,
27017
+ options: {
27018
+ ...chartData.options,
27019
+ scales: {
27020
+ ...chartData.options.scales,
27021
+ x: xScale,
27022
+ },
27023
+ layout: {
27024
+ ...chartData.options.layout,
27025
+ padding: {
27026
+ ...chartData.options.layout?.padding,
27027
+ bottom: 5,
27028
+ },
27029
+ },
27030
+ },
27031
+ };
27032
+ }
27033
+ getAxisLimitsFromDataset(chartData) {
27034
+ const data = chartData.data.datasets.map((ds) => ds.data).flat();
27035
+ const xValues = data.map((d, i) => (typeof d === "object" && d !== null ? d.x : i));
27036
+ const xMin = Math.min(...xValues);
27037
+ const xMax = Math.max(...xValues);
27038
+ return { xMin, xMax };
27039
+ }
27040
+ get shouldAnimate() {
27041
+ return this.env.model.getters.isDashboard() && !this.sliceable;
27042
+ }
27043
+ createChart(chartRuntime) {
27044
+ const chartData = chartRuntime.chartJsConfig;
27045
+ this.isBarChart = chartData.type === "bar";
27046
+ this.chartId = `${chartData.type}-${this.props.chartId}`;
27047
+ this.datasetBoundaries = this.getAxisLimitsFromDataset(chartData);
27048
+ if (this.sliceable) {
27049
+ const updatedData = this.getDetailChartConfiguration(chartData);
27050
+ chartRuntime.chartJsConfig = updatedData;
27051
+ }
27052
+ super.createChart(chartRuntime);
27053
+ this.hasLinearScale = this.chart?.scales?.x.type === "linear";
27054
+ if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
27055
+ return;
27056
+ }
27057
+ this.masterChart?.destroy();
27058
+ const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27059
+ this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
27060
+ this.resetAxesLimits();
27061
+ }
27062
+ updateChartJs(chartRuntime) {
27063
+ const chartData = chartRuntime.chartJsConfig;
27064
+ const newDatasetBoundaries = this.getAxisLimitsFromDataset(chartData);
27065
+ if (this.datasetBoundaries.xMin !== newDatasetBoundaries.xMin ||
27066
+ this.datasetBoundaries.xMax !== newDatasetBoundaries.xMax) {
27067
+ this.store.clearAxisLimits(this.chartId);
27068
+ this.datasetBoundaries = newDatasetBoundaries;
27069
+ }
27070
+ this.isBarChart = chartData?.type === "bar";
27071
+ this.chartId = `${chartData.type}-${this.props.chartId}`;
27072
+ if (this.sliceable) {
27073
+ const updatedData = this.getDetailChartConfiguration(chartData);
27074
+ chartRuntime.chartJsConfig = updatedData;
27075
+ }
27076
+ super.updateChartJs(chartRuntime);
27077
+ this.hasLinearScale = this.chart?.scales?.x.type === "linear";
27078
+ if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
27079
+ this.masterChart = undefined;
27080
+ }
27081
+ else {
27082
+ const masterChartConfig = this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]);
27083
+ if (!this.masterChart) {
27084
+ const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27085
+ this.masterChart = new window.Chart(masterChartCtx, masterChartConfig);
27086
+ }
27087
+ else {
27088
+ this.masterChart.data = masterChartConfig.data;
27089
+ this.masterChart.config.options = masterChartConfig.options;
27090
+ this.masterChart.update();
27091
+ }
27092
+ }
27093
+ this.resetAxesLimits();
27094
+ }
27095
+ resetAxesLimits() {
27096
+ if (!this.chart) {
27097
+ return;
27098
+ }
27099
+ const previousAxisLimits = this.store.originalAxisLimits[this.chartId];
27100
+ if (previousAxisLimits?.x?.min === undefined && previousAxisLimits?.x?.max === undefined) {
27101
+ let scales = this.masterChart
27102
+ ? this.masterChart.scales
27103
+ : this.chart.scales;
27104
+ if (!this.hasLinearScale && scales?.x) {
27105
+ scales = {
27106
+ ...scales,
27107
+ x: {
27108
+ min: Math.ceil(scales.x.min) - this.axisOffset,
27109
+ max: Math.floor(scales.x.max) + this.axisOffset,
27110
+ },
27111
+ };
27112
+ }
27113
+ this.store.resetAxisLimits(this.chartId, scales);
27114
+ return;
27115
+ }
27116
+ this.updateTrendingLineAxes();
27117
+ this.chart.update();
27118
+ if (!this.masterChart) {
27119
+ return;
27120
+ }
27121
+ this.masterChart.update();
27122
+ }
27123
+ updateTrendingLineAxes() {
27124
+ this.store.updateTrendLineConfiguration(this.chartId);
27125
+ const config = this.store.currentAxesLimits[this.chartId];
27126
+ for (const axisId of [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID]) {
27127
+ if (!this.chart?.config.options?.scales?.[axisId] || !config?.[axisId]) {
27128
+ continue;
27129
+ }
27130
+ this.chart.config.options.scales[axisId].min = config[axisId].min;
27131
+ this.chart.config.options.scales[axisId].max = config[axisId].max;
27132
+ }
27133
+ }
27134
+ get upperBound() {
27135
+ return this.computePosition(this.store.currentAxesLimits[this.chartId]?.x?.max);
27136
+ }
27137
+ get lowerBound() {
27138
+ return this.computePosition(this.store.currentAxesLimits[this.chartId]?.x?.min);
27139
+ }
27140
+ computePosition(value) {
27141
+ if (value === undefined || !this.masterChart?.scales?.x) {
27142
+ return undefined;
27143
+ }
27144
+ const scale = this.masterChart.scales.x;
27145
+ if (this.hasLinearScale) {
27146
+ return scale.getPixelForValue(value);
27147
+ }
27148
+ if (!this.masterChart.chartArea) {
27149
+ return undefined;
27150
+ }
27151
+ const { left, right } = this.masterChart.chartArea;
27152
+ const { min, max } = scale;
27153
+ const offset = this.axisOffset;
27154
+ return left + ((right - left) * (offset + value - min)) / (2 * offset + max - min);
27155
+ }
27156
+ computeCoordinate(position) {
27157
+ if (!this.masterChart) {
27158
+ return undefined;
27159
+ }
27160
+ const scale = this.masterChart.scales.x;
27161
+ if (this.hasLinearScale) {
27162
+ const value = scale.getValueForPixel(position);
27163
+ if (value === undefined) {
27164
+ return undefined;
27165
+ }
27166
+ return Math.round(value * 100) / 100;
27167
+ }
27168
+ const { left, right } = this.masterChart.chartArea;
27169
+ const offset = this.axisOffset;
27170
+ return (scale.min -
27171
+ offset +
27172
+ ((scale.max + 2 * offset - scale.min) * (position - left)) / (right - left));
27173
+ }
27174
+ updateAxisLimits(xMin, xMax) {
27175
+ if (!this.hasLinearScale) {
27176
+ this.chart.config.options.scales.x.min = Math.ceil(xMin);
27177
+ this.chart.config.options.scales.x.max = Math.floor(xMax);
27178
+ }
27179
+ else {
27180
+ this.chart.config.options.scales.x.min = xMin;
27181
+ this.chart.config.options.scales.x.max = xMax;
27182
+ }
27183
+ this.store.updateAxisLimits(this.chartId, { min: xMin, max: xMax });
27184
+ this.updateTrendingLineAxes();
27185
+ this.masterChart?.update();
27186
+ this.chart?.update();
27187
+ }
27188
+ onPointerDownInMasterChart(ev) {
27189
+ this.removeEventListeners();
27190
+ const position = ev.offsetX;
27191
+ if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27192
+ return;
27193
+ }
27194
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27195
+ const xMax = this.upperBound ?? right;
27196
+ const xMin = this.lowerBound ?? left;
27197
+ if (position < left - 5 || position > right + 5 || ev.offsetY < top || ev.offsetY > bottom) {
27198
+ return;
27199
+ }
27200
+ ev.preventDefault();
27201
+ ev.stopPropagation();
27202
+ let startingPositionOnChart, windowSize, startX;
27203
+ const startingEventPosition = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27204
+ if ((xMin !== left || xMax !== right) && position > xMin + 5 && position < xMax - 5) {
27205
+ startingPositionOnChart = ev.offsetX - xMin;
27206
+ this.mode = "moveInMaster";
27207
+ const currentLimits = this.store.currentAxesLimits[this.chartId]?.x;
27208
+ windowSize =
27209
+ (currentLimits?.max ?? this.chart.scales.x.max) -
27210
+ (currentLimits?.min ?? this.chart.scales.x.min);
27211
+ }
27212
+ else {
27213
+ this.mode = "selectInMaster";
27214
+ if (Math.abs(position - xMin) < 5) {
27215
+ startingPositionOnChart = xMax;
27216
+ }
27217
+ else if (Math.abs(position - xMax) < 5) {
27218
+ startingPositionOnChart = xMin;
27219
+ }
27220
+ else {
27221
+ startingPositionOnChart = clip(position, left, right);
27222
+ }
27223
+ startX = this.computeCoordinate(startingPositionOnChart);
27224
+ }
27225
+ const originalXMin = this.store.originalAxisLimits[this.chartId].x.min;
27226
+ const originalXMax = this.store.originalAxisLimits[this.chartId].x.max;
27227
+ const computeNewAxisLimits = (position) => {
27228
+ let xMin, xMax;
27229
+ const { left, right } = this.masterChart.chartArea;
27230
+ if (this.mode === "moveInMaster") {
27231
+ xMin = this.computeCoordinate(position - startingPositionOnChart);
27232
+ if (xMin < originalXMin) {
27233
+ xMin = originalXMin;
27234
+ }
27235
+ else if (xMin > originalXMax - windowSize) {
27236
+ xMin = originalXMax - windowSize;
27237
+ }
27238
+ xMax = xMin + windowSize;
27239
+ }
27240
+ else if (this.mode === "selectInMaster") {
27241
+ const upperBound = clip(position, left, right);
27242
+ if (Math.abs(startingPositionOnChart - upperBound) > 5) {
27243
+ const endX = this.computeCoordinate(upperBound);
27244
+ if (startX === undefined || endX === undefined) {
27245
+ return {};
27246
+ }
27247
+ xMin = Math.min(startX, endX);
27248
+ xMax = Math.max(startX, endX);
27249
+ }
27250
+ }
27251
+ return { min: xMin, max: xMax };
27252
+ };
27253
+ const onDragFromMasterChart = (ev) => {
27254
+ const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27255
+ if (Math.abs(position - startingEventPosition) < 5) {
27256
+ return;
27257
+ }
27258
+ const { min: xMin, max: xMax } = computeNewAxisLimits(position);
27259
+ if (xMin !== undefined && xMax !== undefined) {
27260
+ this.updateAxisLimits(xMin, xMax);
27261
+ }
27262
+ };
27263
+ const onPointerUpInMasterChart = (ev) => {
27264
+ this.removeEventListeners();
27265
+ const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27266
+ if (Math.abs(position - startingEventPosition) > 5) {
27267
+ let { min: xMin, max: xMax } = computeNewAxisLimits(position);
27268
+ if (xMin !== undefined && xMax !== undefined) {
27269
+ if (!this.hasLinearScale) {
27270
+ if (this.mode === "moveInMaster" && windowSize && !this.isBarChart) {
27271
+ xMin = Math.round(xMin) - this.axisOffset;
27272
+ xMax = xMin + windowSize;
27273
+ }
27274
+ else {
27275
+ xMin = Math.ceil(xMin) - this.axisOffset;
27276
+ xMax = Math.floor(xMax) + this.axisOffset;
27277
+ }
27278
+ }
27279
+ this.updateAxisLimits(xMin, xMax);
27280
+ }
27281
+ }
27282
+ this.mode = undefined;
27283
+ };
27284
+ this.removeEventListeners = () => {
27285
+ window.removeEventListener("pointermove", onDragFromMasterChart, true);
27286
+ window.removeEventListener("pointerup", onPointerUpInMasterChart, true);
27287
+ };
27288
+ window.addEventListener("pointermove", onDragFromMasterChart, true);
27289
+ window.addEventListener("pointerup", onPointerUpInMasterChart, true);
27290
+ }
27291
+ onPointerMoveInMasterChart(ev) {
27292
+ const { offsetX: x, offsetY: y } = ev;
27293
+ if (this.mode === undefined) {
27294
+ const target = ev.target;
27295
+ if (!this.masterChart?.chartArea) {
27296
+ target["style"].cursor = "default";
27297
+ return;
27298
+ }
27299
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27300
+ const start = this.lowerBound ?? left;
27301
+ const end = this.upperBound ?? right;
27302
+ if (y < top || y > bottom) {
27303
+ target["style"].cursor = "default";
27304
+ }
27305
+ else if (Math.abs(start - x) < 5 || Math.abs(end - x) < 5) {
27306
+ target["style"].cursor = "ew-resize";
27307
+ }
27308
+ else if (start < x && x < end && (start !== left || end !== right)) {
27309
+ target["style"].cursor = "grab";
27310
+ }
27311
+ else {
27312
+ target["style"].cursor = "crosshair";
27313
+ }
27314
+ }
27315
+ }
27316
+ onMouseLeaveMasterChart(ev) {
27317
+ const target = ev.target;
27318
+ if (!target) {
27319
+ return;
27320
+ }
27321
+ target["style"].cursor = "default";
27322
+ }
27323
+ onDoubleClickInMasterChart(ev) {
27324
+ this.mode = undefined;
27325
+ const position = ev.offsetX;
27326
+ if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27327
+ return;
27328
+ }
27329
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27330
+ let upperBound = this.upperBound ?? right;
27331
+ let lowerBound = this.lowerBound ?? left;
27332
+ if (upperBound < lowerBound) {
27333
+ [upperBound, lowerBound] = [lowerBound, upperBound];
27334
+ }
27335
+ if (position < left - 5 || position > right + 5 || ev.offsetY < top || ev.offsetY > bottom) {
27336
+ return;
27337
+ }
27338
+ ev.preventDefault();
27339
+ ev.stopPropagation();
27340
+ let { min: xMin, max: xMax } = this.store.currentAxesLimits[this.chartId]?.x ?? this.chart.scales.x;
27341
+ const originalAxisLimits = this.store.originalAxisLimits[this.chartId].x;
27342
+ if (!originalAxisLimits) {
27343
+ return;
27344
+ }
27345
+ let originalXMin = originalAxisLimits.min;
27346
+ let originalXMax = originalAxisLimits.max;
27347
+ if (this.hasLinearScale) {
27348
+ originalXMin = Math.ceil(originalXMin) - this.axisOffset;
27349
+ originalXMax = Math.floor(originalXMax) + this.axisOffset;
27350
+ }
27351
+ if (Math.abs(position - lowerBound) < 5) {
27352
+ // Reset to original min
27353
+ xMin = originalXMin;
27354
+ }
27355
+ else if (Math.abs(position - upperBound) < 5) {
27356
+ xMax = originalXMax;
27357
+ }
27358
+ else if (lowerBound < position && position < upperBound) {
27359
+ // Reset to original limits
27360
+ xMin = originalXMin;
27361
+ xMax = originalXMax;
27362
+ }
27363
+ else {
27364
+ return;
27365
+ }
27366
+ this.updateAxisLimits(xMin, xMax);
27367
+ }
27368
+ }
27369
+
26727
27370
  const cellAnimationRegistry = new Registry();
26728
27371
  cellAnimationRegistry.add("animatedBackgroundColorChange", {
26729
27372
  id: "animatedBackgroundColorChange",
@@ -27086,13 +27729,13 @@ stores.inject(MyMetaStore, storeInstance);
27086
27729
  class GaugeChartComponent extends owl.Component {
27087
27730
  static template = "o-spreadsheet-GaugeChartComponent";
27088
27731
  static props = {
27089
- figureUI: Object,
27732
+ chartId: String,
27090
27733
  isFullScreen: { type: Boolean, optional: true },
27091
27734
  };
27092
27735
  canvas = owl.useRef("chartContainer");
27093
27736
  animationStore;
27094
27737
  get runtime() {
27095
- return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
27738
+ return this.env.model.getters.getChartRuntime(this.props.chartId);
27096
27739
  }
27097
27740
  setup() {
27098
27741
  if (this.env.model.getters.isDashboard()) {
@@ -27140,9 +27783,7 @@ stores.inject(MyMetaStore, storeInstance);
27140
27783
  return this.canvas.el;
27141
27784
  }
27142
27785
  get animationFigureId() {
27143
- return this.props.isFullScreen
27144
- ? this.props.figureUI.id + "-fullscreen"
27145
- : this.props.figureUI.id;
27786
+ return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
27146
27787
  }
27147
27788
  }
27148
27789
  /**
@@ -27199,6 +27840,7 @@ stores.inject(MyMetaStore, storeInstance);
27199
27840
  type = "combo";
27200
27841
  showValues;
27201
27842
  hideDataMarkers;
27843
+ zoomable;
27202
27844
  constructor(definition, sheetId, getters) {
27203
27845
  super(definition, sheetId, getters);
27204
27846
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -27211,6 +27853,7 @@ stores.inject(MyMetaStore, storeInstance);
27211
27853
  this.axesDesign = definition.axesDesign;
27212
27854
  this.showValues = definition.showValues;
27213
27855
  this.hideDataMarkers = definition.hideDataMarkers;
27856
+ this.zoomable = definition.zoomable;
27214
27857
  }
27215
27858
  static transformDefinition(chartSheetId, definition, applyChange) {
27216
27859
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -27260,6 +27903,7 @@ stores.inject(MyMetaStore, storeInstance);
27260
27903
  axesDesign: this.axesDesign,
27261
27904
  showValues: this.showValues,
27262
27905
  hideDataMarkers: this.hideDataMarkers,
27906
+ zoomable: this.zoomable,
27263
27907
  };
27264
27908
  }
27265
27909
  getDefinitionForExcel() {
@@ -27303,6 +27947,7 @@ stores.inject(MyMetaStore, storeInstance);
27303
27947
  axesDesign: context.axesDesign,
27304
27948
  showValues: context.showValues,
27305
27949
  hideDataMarkers: context.hideDataMarkers,
27950
+ zoomable: context.zoomable,
27306
27951
  };
27307
27952
  }
27308
27953
  duplicateInDuplicatedSheet(newSheetId) {
@@ -27908,6 +28553,7 @@ stores.inject(MyMetaStore, storeInstance);
27908
28553
  fillArea;
27909
28554
  showValues;
27910
28555
  hideDataMarkers;
28556
+ zoomable;
27911
28557
  constructor(definition, sheetId, getters) {
27912
28558
  super(definition, sheetId, getters);
27913
28559
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -27924,6 +28570,7 @@ stores.inject(MyMetaStore, storeInstance);
27924
28570
  this.fillArea = definition.fillArea;
27925
28571
  this.showValues = definition.showValues;
27926
28572
  this.hideDataMarkers = definition.hideDataMarkers;
28573
+ this.zoomable = definition.zoomable;
27927
28574
  }
27928
28575
  static validateChartDefinition(validator, definition) {
27929
28576
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -27948,6 +28595,7 @@ stores.inject(MyMetaStore, storeInstance);
27948
28595
  fillArea: context.fillArea,
27949
28596
  showValues: context.showValues,
27950
28597
  hideDataMarkers: context.hideDataMarkers,
28598
+ zoomable: context.zoomable,
27951
28599
  };
27952
28600
  }
27953
28601
  getDefinition() {
@@ -27979,6 +28627,7 @@ stores.inject(MyMetaStore, storeInstance);
27979
28627
  fillArea: this.fillArea,
27980
28628
  showValues: this.showValues,
27981
28629
  hideDataMarkers: this.hideDataMarkers,
28630
+ zoomable: this.zoomable,
27982
28631
  };
27983
28632
  }
27984
28633
  getContextCreation() {
@@ -28501,6 +29150,7 @@ stores.inject(MyMetaStore, storeInstance);
28501
29150
  dataSetDesign;
28502
29151
  axesDesign;
28503
29152
  showValues;
29153
+ zoomable;
28504
29154
  constructor(definition, sheetId, getters) {
28505
29155
  super(definition, sheetId, getters);
28506
29156
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -28513,6 +29163,7 @@ stores.inject(MyMetaStore, storeInstance);
28513
29163
  this.dataSetDesign = definition.dataSets;
28514
29164
  this.axesDesign = definition.axesDesign;
28515
29165
  this.showValues = definition.showValues;
29166
+ this.zoomable = definition.zoomable;
28516
29167
  }
28517
29168
  static validateChartDefinition(validator, definition) {
28518
29169
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -28533,6 +29184,7 @@ stores.inject(MyMetaStore, storeInstance);
28533
29184
  aggregated: context.aggregated ?? false,
28534
29185
  axesDesign: context.axesDesign,
28535
29186
  showValues: context.showValues,
29187
+ zoomable: context.zoomable,
28536
29188
  };
28537
29189
  }
28538
29190
  getDefinition() {
@@ -28560,6 +29212,7 @@ stores.inject(MyMetaStore, storeInstance);
28560
29212
  aggregated: this.aggregated,
28561
29213
  axesDesign: this.axesDesign,
28562
29214
  showValues: this.showValues,
29215
+ zoomable: this.zoomable,
28563
29216
  };
28564
29217
  }
28565
29218
  getContextCreation() {
@@ -28944,6 +29597,7 @@ stores.inject(MyMetaStore, storeInstance);
28944
29597
  dataSetDesign;
28945
29598
  axesDesign;
28946
29599
  showValues;
29600
+ zoomable;
28947
29601
  constructor(definition, sheetId, getters) {
28948
29602
  super(definition, sheetId, getters);
28949
29603
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -28962,6 +29616,7 @@ stores.inject(MyMetaStore, storeInstance);
28962
29616
  this.dataSetDesign = definition.dataSets;
28963
29617
  this.axesDesign = definition.axesDesign;
28964
29618
  this.showValues = definition.showValues;
29619
+ this.zoomable = definition.zoomable;
28965
29620
  }
28966
29621
  static transformDefinition(chartSheetId, definition, applyChange) {
28967
29622
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -28985,6 +29640,7 @@ stores.inject(MyMetaStore, storeInstance);
28985
29640
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
28986
29641
  axesDesign: context.axesDesign,
28987
29642
  showValues: context.showValues,
29643
+ zoomable: context.zoomable ?? false,
28988
29644
  };
28989
29645
  }
28990
29646
  getContextCreation() {
@@ -29044,6 +29700,7 @@ stores.inject(MyMetaStore, storeInstance);
29044
29700
  firstValueAsSubtotal: this.firstValueAsSubtotal,
29045
29701
  axesDesign: this.axesDesign,
29046
29702
  showValues: this.showValues,
29703
+ zoomable: this.zoomable,
29047
29704
  };
29048
29705
  }
29049
29706
  getDefinitionForExcel() {
@@ -29220,14 +29877,14 @@ stores.inject(MyMetaStore, storeInstance);
29220
29877
  sequence: 100,
29221
29878
  });
29222
29879
  const chartComponentRegistry = new Registry();
29223
- chartComponentRegistry.add("line", ChartJsComponent);
29224
- chartComponentRegistry.add("bar", ChartJsComponent);
29225
- chartComponentRegistry.add("combo", ChartJsComponent);
29880
+ chartComponentRegistry.add("line", ZoomableChartJsComponent);
29881
+ chartComponentRegistry.add("bar", ZoomableChartJsComponent);
29882
+ chartComponentRegistry.add("combo", ZoomableChartJsComponent);
29226
29883
  chartComponentRegistry.add("pie", ChartJsComponent);
29227
29884
  chartComponentRegistry.add("gauge", GaugeChartComponent);
29228
- chartComponentRegistry.add("scatter", ChartJsComponent);
29885
+ chartComponentRegistry.add("scatter", ZoomableChartJsComponent);
29229
29886
  chartComponentRegistry.add("scorecard", ScorecardChart);
29230
- chartComponentRegistry.add("waterfall", ChartJsComponent);
29887
+ chartComponentRegistry.add("waterfall", ZoomableChartJsComponent);
29231
29888
  chartComponentRegistry.add("pyramid", ChartJsComponent);
29232
29889
  chartComponentRegistry.add("radar", ChartJsComponent);
29233
29890
  chartComponentRegistry.add("geo", ChartJsComponent);
@@ -29426,12 +30083,70 @@ stores.inject(MyMetaStore, storeInstance);
29426
30083
  preview: "o-spreadsheet-ChartPreview.TREE_MAP_CHART",
29427
30084
  });
29428
30085
 
30086
+ function generateMasterChartConfig(chartJsConfig) {
30087
+ return {
30088
+ ...chartJsConfig,
30089
+ data: {
30090
+ ...chartJsConfig.data,
30091
+ datasets: chartJsConfig.data.datasets
30092
+ .filter((ds) => !isTrendLineAxis(ds["xAxisID"]))
30093
+ .map((ds) => ({
30094
+ ...ds,
30095
+ pointRadius: 0,
30096
+ showLine: true,
30097
+ })),
30098
+ },
30099
+ options: {
30100
+ ...chartJsConfig.options,
30101
+ hover: { mode: null },
30102
+ plugins: {
30103
+ ...chartJsConfig.options.plugins,
30104
+ title: { display: false },
30105
+ legend: { display: false },
30106
+ tooltip: { enabled: false },
30107
+ chartShowValuesPlugin: undefined,
30108
+ },
30109
+ layout: {
30110
+ padding: {
30111
+ ...chartJsConfig.options.layout?.padding,
30112
+ top: 5,
30113
+ bottom: 10,
30114
+ },
30115
+ },
30116
+ scales: {
30117
+ y: {
30118
+ ...chartJsConfig.options.scales?.y,
30119
+ display: false,
30120
+ },
30121
+ y1: {
30122
+ ...chartJsConfig.options.scales?.y1,
30123
+ display: false,
30124
+ },
30125
+ x: {
30126
+ ...chartJsConfig.options.scales?.x,
30127
+ title: undefined,
30128
+ ticks: {
30129
+ ...chartJsConfig.options.scales?.x?.ticks,
30130
+ callback: function (value) {
30131
+ return truncateLabel(chartJsConfig.options.scales?.x?.ticks?.callback?.call(this, value), 5);
30132
+ },
30133
+ padding: 0,
30134
+ font: {
30135
+ size: 9,
30136
+ },
30137
+ },
30138
+ },
30139
+ },
30140
+ },
30141
+ };
30142
+ }
30143
+
29429
30144
  /**
29430
30145
  * Create a function used to create a Chart based on the definition
29431
30146
  */
29432
30147
  function chartFactory(getters) {
29433
30148
  const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
29434
- function createChart(id, definition, sheetId) {
30149
+ function createChart(figureId, definition, sheetId) {
29435
30150
  const builder = builders.find((builder) => builder.match(definition.type));
29436
30151
  if (!builder) {
29437
30152
  throw new Error(`No builder for this chart: ${definition.type}`);
@@ -29451,7 +30166,13 @@ stores.inject(MyMetaStore, storeInstance);
29451
30166
  if (!builder) {
29452
30167
  throw new Error("No runtime builder for this chart.");
29453
30168
  }
29454
- return builder.getChartRuntime(chart, getters);
30169
+ const runtime = builder.getChartRuntime(chart, getters);
30170
+ const definition = chart.getDefinition();
30171
+ if ("chartJsConfig" in runtime && /line|combo|bar|scatter|waterfall/.test(definition.type)) {
30172
+ const chartJsConfig = runtime.chartJsConfig;
30173
+ runtime["masterChartConfig"] = generateMasterChartConfig(chartJsConfig);
30174
+ }
30175
+ return runtime;
29455
30176
  }
29456
30177
  return createRuntimeChart;
29457
30178
  }
@@ -29782,6 +30503,8 @@ stores.inject(MyMetaStore, storeInstance);
29782
30503
  return {
29783
30504
  relsFiles: [],
29784
30505
  sharedStrings: [],
30506
+ chartIds: [],
30507
+ imageIds: [],
29785
30508
  // default Values that will always be part of the style sheet
29786
30509
  styles: [
29787
30510
  {
@@ -29859,6 +30582,10 @@ stores.inject(MyMetaStore, storeInstance);
29859
30582
  }
29860
30583
 
29861
30584
  function getChartMenuActions(figureId, onFigureDeleted, env) {
30585
+ const chartId = env.model.getters.getChartIdFromFigureId(figureId);
30586
+ if (!chartId) {
30587
+ return [];
30588
+ }
29862
30589
  const menuItemSpecs = [
29863
30590
  {
29864
30591
  id: "edit",
@@ -29881,8 +30608,8 @@ stores.inject(MyMetaStore, storeInstance);
29881
30608
  execute: async () => {
29882
30609
  const figureSheetId = env.model.getters.getFigureSheetId(figureId);
29883
30610
  const figure = env.model.getters.getFigure(figureSheetId, figureId);
29884
- const chartType = env.model.getters.getChartType(figureId);
29885
- const runtime = env.model.getters.getChartRuntime(figureId);
30611
+ const chartType = env.model.getters.getChartType(chartId);
30612
+ const runtime = env.model.getters.getChartRuntime(chartId);
29886
30613
  const imageUrl = chartToImageUrl(runtime, figure, chartType);
29887
30614
  const innerHTML = `<img src="${xmlEscape(imageUrl)}" />`;
29888
30615
  const blob = await chartToImageFile(runtime, figure, chartType);
@@ -29902,8 +30629,8 @@ stores.inject(MyMetaStore, storeInstance);
29902
30629
  execute: async () => {
29903
30630
  const figureSheetId = env.model.getters.getFigureSheetId(figureId);
29904
30631
  const figure = env.model.getters.getFigure(figureSheetId, figureId);
29905
- const chartType = env.model.getters.getChartType(figureId);
29906
- const runtime = env.model.getters.getChartRuntime(figureId);
30632
+ const chartType = env.model.getters.getChartType(chartId);
30633
+ const runtime = env.model.getters.getChartRuntime(chartId);
29907
30634
  const url = chartToImageUrl(runtime, figure, chartType);
29908
30635
  downloadFile(url, "chart");
29909
30636
  },
@@ -29962,6 +30689,35 @@ stores.inject(MyMetaStore, storeInstance);
29962
30689
  ];
29963
30690
  return createActions(menuItemSpecs);
29964
30691
  }
30692
+ function getCarouselMenuActions(figureId, onFigureDeleted, env) {
30693
+ const menuItemSpecs = [
30694
+ {
30695
+ id: "edit_carousel",
30696
+ name: _t("Edit carousel"),
30697
+ sequence: 1,
30698
+ execute: () => {
30699
+ env.model.dispatch("SELECT_FIGURE", { figureId });
30700
+ env.openSidePanel("CarouselPanel", { figureId });
30701
+ },
30702
+ icon: "o-spreadsheet-Icon.EDIT",
30703
+ isEnabled: (env) => !env.isSmall,
30704
+ },
30705
+ {
30706
+ id: "edit_chart",
30707
+ name: _t("Edit chart"),
30708
+ sequence: 1,
30709
+ execute: () => {
30710
+ env.model.dispatch("SELECT_FIGURE", { figureId });
30711
+ env.openSidePanel("ChartPanel", {});
30712
+ },
30713
+ icon: "o-spreadsheet-Icon.EDIT",
30714
+ isEnabled: (env) => !env.isSmall,
30715
+ isVisible: (env) => env.model.getters.getSelectedCarouselItem(figureId)?.type === "chart",
30716
+ },
30717
+ getDeleteMenuItem(figureId, onFigureDeleted, env),
30718
+ ];
30719
+ return createActions(menuItemSpecs).filter((action) => env.model.getters.isReadonly() ? action.isReadonlyAllowed : true);
30720
+ }
29965
30721
  function getCopyMenuItem(figureId, env, copiedNotificationMessage) {
29966
30722
  return {
29967
30723
  id: "copy",
@@ -30010,24 +30766,101 @@ stores.inject(MyMetaStore, storeInstance);
30010
30766
  };
30011
30767
  }
30012
30768
 
30013
- class FullScreenChartStore extends SpreadsheetStore {
30014
- mutators = ["toggleFullScreenChart"];
30015
- fullScreenFigure = undefined;
30016
- toggleFullScreenChart(figureId) {
30017
- if (this.fullScreenFigure?.id === figureId) {
30018
- this.fullScreenFigure = undefined;
30769
+ const CAROUSEL_DEFAULT_CHART_DEFINITION = {
30770
+ type: "bar",
30771
+ title: {},
30772
+ stacked: false,
30773
+ dataSetsHaveTitle: false,
30774
+ dataSets: [],
30775
+ legendPosition: "top",
30776
+ };
30777
+ function getCarouselItemPreview(getters, item) {
30778
+ if (item.type === "carouselDataView") {
30779
+ return "o-spreadsheet-Icon.LIST";
30780
+ }
30781
+ const definition = getters.getChartDefinition(item.chartId);
30782
+ const matchedChart = chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) ||
30783
+ chartSubtypeRegistry.get(definition.type);
30784
+ return matchedChart.preview;
30785
+ }
30786
+ function getCarouselItemTitle(getters, item) {
30787
+ if (item.title) {
30788
+ return item.title;
30789
+ }
30790
+ if (item.type === "carouselDataView") {
30791
+ return _t("Data");
30792
+ }
30793
+ const definition = getters.getChartDefinition(item.chartId);
30794
+ const matchedChart = chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) ||
30795
+ chartSubtypeRegistry.get(definition.type);
30796
+ return matchedChart.displayName;
30797
+ }
30798
+
30799
+ class CarouselFigure extends owl.Component {
30800
+ static template = "o-spreadsheet-CarouselFigure";
30801
+ static props = {
30802
+ figureUI: Object,
30803
+ onFigureDeleted: Function,
30804
+ editFigureStyle: { type: Function, optional: true },
30805
+ };
30806
+ static components = {};
30807
+ setup() {
30808
+ owl.useEffect(() => {
30809
+ if (this.selectedCarouselItem?.type === "carouselDataView") {
30810
+ this.props.editFigureStyle?.({ "pointer-events": "none" });
30811
+ }
30812
+ else {
30813
+ this.props.editFigureStyle?.({ "pointer-events": "auto" });
30814
+ }
30815
+ });
30816
+ }
30817
+ get carousel() {
30818
+ return this.env.model.getters.getCarousel(this.props.figureUI.id);
30819
+ }
30820
+ get selectedCarouselItem() {
30821
+ return this.env.model.getters.getSelectedCarouselItem(this.props.figureUI.id);
30822
+ }
30823
+ get chartComponent() {
30824
+ const selectedItem = this.selectedCarouselItem;
30825
+ if (selectedItem?.type !== "chart") {
30826
+ throw new Error("Selected item is not a chart");
30019
30827
  }
30020
- else {
30021
- this.makeFullScreen(figureId);
30828
+ const type = this.env.model.getters.getChartType(selectedItem.chartId);
30829
+ const component = chartComponentRegistry.get(type);
30830
+ if (!component) {
30831
+ throw new Error(`Component is not defined for type ${type}`);
30022
30832
  }
30833
+ return component;
30023
30834
  }
30024
- makeFullScreen(figureId) {
30025
- const sheetId = this.getters.getActiveSheetId();
30026
- const figure = this.getters.getFigure(sheetId, figureId);
30027
- if (figure) {
30028
- this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
30835
+ onCarouselDoubleClick() {
30836
+ this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureUI.id });
30837
+ this.env.openSidePanel("CarouselPanel", { figureId: this.props.figureUI.id });
30838
+ }
30839
+ isItemSelected(item) {
30840
+ const selectedItem = this.selectedCarouselItem;
30841
+ return deepEquals(selectedItem, item);
30842
+ }
30843
+ getItemTitle(item) {
30844
+ return getCarouselItemTitle(this.env.model.getters, item);
30845
+ }
30846
+ onCarouselTabClick(item) {
30847
+ this.env.model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
30848
+ figureId: this.props.figureUI.id,
30849
+ sheetId: this.env.model.getters.getActiveSheetId(),
30850
+ item,
30851
+ });
30852
+ if (item.type === "carouselDataView" &&
30853
+ this.env.model.getters.getSelectedFigureId() === this.props.figureUI.id) {
30854
+ this.env.model.dispatch("SELECT_FIGURE", { figureId: null });
30029
30855
  }
30030
30856
  }
30857
+ get headerStyle() {
30858
+ const cssProperties = {};
30859
+ if (this.selectedCarouselItem?.type === "carouselDataView") {
30860
+ cssProperties["background-color"] = "#ffffff";
30861
+ }
30862
+ return cssPropertiesToCss(cssProperties);
30863
+ }
30031
30864
  }
30032
30865
 
30033
30866
  /**
@@ -30794,9 +31627,9 @@ stores.inject(MyMetaStore, storeInstance);
30794
31627
  this.originalChartDefinition = this.getters.getChartDefinition(chartId);
30795
31628
  }
30796
31629
  updateType(type) {
30797
- const figureId = this.chartId;
30798
- const currentDefinition = this.getters.getChartDefinition(figureId);
30799
- if (currentDefinition.type === type) {
31630
+ const chartId = this.chartId;
31631
+ const currentDefinition = this.getters.getChartDefinition(chartId);
31632
+ if (currentDefinition?.type === type) {
30800
31633
  return;
30801
31634
  }
30802
31635
  let definition;
@@ -30806,7 +31639,7 @@ stores.inject(MyMetaStore, storeInstance);
30806
31639
  else {
30807
31640
  const newChartInfo = chartSubtypeRegistry.get(type);
30808
31641
  const ChartClass = chartRegistry.get(newChartInfo.chartType);
30809
- const chartCreationContext = this.getters.getContextCreationChart(figureId);
31642
+ const chartCreationContext = this.getters.getContextCreationChart(chartId);
30810
31643
  if (!chartCreationContext)
30811
31644
  return;
30812
31645
  definition = {
@@ -30816,7 +31649,8 @@ stores.inject(MyMetaStore, storeInstance);
30816
31649
  }
30817
31650
  this.model.dispatch("UPDATE_CHART", {
30818
31651
  definition,
30819
- figureId,
31652
+ chartId,
31653
+ figureId: this.getters.getFigureIdFromChartId(chartId),
30820
31654
  sheetId: this.getters.getActiveSheetId(),
30821
31655
  });
30822
31656
  }
@@ -30837,17 +31671,17 @@ stores.inject(MyMetaStore, storeInstance);
30837
31671
  class ChartDashboardMenu extends owl.Component {
30838
31672
  static template = "o-spreadsheet-ChartDashboardMenu";
30839
31673
  static components = { MenuPopover };
30840
- static props = { figureUI: Object };
31674
+ static props = { chartId: String };
30841
31675
  fullScreenFigureStore;
30842
31676
  store;
30843
31677
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
30844
31678
  setup() {
30845
31679
  super.setup();
30846
- this.store = useLocalStore(ChartDashboardMenuStore, this.props.figureUI.id);
31680
+ this.store = useLocalStore(ChartDashboardMenuStore, this.props.chartId);
30847
31681
  this.fullScreenFigureStore = useStore(FullScreenChartStore);
30848
- owl.onWillUpdateProps(({ figureUI }) => {
30849
- if (figureUI.id !== this.props.figureUI.id) {
30850
- this.store.reset(figureUI.id);
31682
+ owl.onWillUpdateProps(({ chartId }) => {
31683
+ if (chartId !== this.props.chartId) {
31684
+ this.store.reset(chartId);
30851
31685
  }
30852
31686
  });
30853
31687
  }
@@ -30855,26 +31689,28 @@ stores.inject(MyMetaStore, storeInstance);
30855
31689
  return [this.fullScreenMenuItem, ...this.store.changeChartTypeMenuItems].filter(isDefined);
30856
31690
  }
30857
31691
  get backgroundColor() {
30858
- const color = this.env.model.getters.getChartDefinition(this.props.figureUI.id).background;
31692
+ const color = this.env.model.getters.getChartDefinition(this.props.chartId).background;
30859
31693
  return "background-color: " + (color || BACKGROUND_CHART_COLOR);
30860
31694
  }
30861
31695
  openContextMenu(ev) {
30862
31696
  this.menuState.isOpen = true;
30863
31697
  this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
30864
- this.menuState.menuItems = getChartMenuActions(this.props.figureUI.id, () => { }, this.env);
31698
+ const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
31699
+ this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
30865
31700
  }
30866
31701
  get fullScreenMenuItem() {
30867
- const definition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
31702
+ const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
31703
+ const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
30868
31704
  if (definition.type === "scorecard") {
30869
31705
  return undefined;
30870
31706
  }
30871
- if (this.props.figureUI.id === this.fullScreenFigureStore.fullScreenFigure?.id) {
31707
+ if (this.props.chartId === this.fullScreenFigureStore.fullScreenFigure?.id) {
30872
31708
  return {
30873
31709
  id: "fullScreenChart",
30874
31710
  label: _t("Exit Full Screen"),
30875
31711
  iconClass: "fa fa-compress",
30876
31712
  onClick: () => {
30877
- this.fullScreenFigureStore.toggleFullScreenChart(this.props.figureUI.id);
31713
+ this.fullScreenFigureStore.toggleFullScreenChart(figureId);
30878
31714
  },
30879
31715
  };
30880
31716
  }
@@ -30883,27 +31719,18 @@ stores.inject(MyMetaStore, storeInstance);
30883
31719
  label: _t("Full Screen"),
30884
31720
  iconClass: "fa fa-expand",
30885
31721
  onClick: () => {
30886
- this.fullScreenFigureStore.toggleFullScreenChart(this.props.figureUI.id);
31722
+ this.fullScreenFigureStore.toggleFullScreenChart(figureId);
30887
31723
  },
30888
31724
  };
30889
31725
  }
30890
31726
  }
30891
31727
 
30892
- // -----------------------------------------------------------------------------
30893
- // STYLE
30894
- // -----------------------------------------------------------------------------
30895
- css /* scss */ `
30896
- .o-chart-container {
30897
- width: 100%;
30898
- height: 100%;
30899
- position: relative;
30900
- }
30901
- `;
30902
31728
  class ChartFigure extends owl.Component {
30903
31729
  static template = "o-spreadsheet-ChartFigure";
30904
31730
  static props = {
30905
31731
  figureUI: Object,
30906
31732
  onFigureDeleted: Function,
31733
+ editFigureStyle: { type: Function, optional: true },
30907
31734
  };
30908
31735
  static components = { ChartDashboardMenu };
30909
31736
  onDoubleClick() {
@@ -30911,7 +31738,14 @@ stores.inject(MyMetaStore, storeInstance);
30911
31738
  this.env.openSidePanel("ChartPanel");
30912
31739
  }
30913
31740
  get chartType() {
30914
- return this.env.model.getters.getChartType(this.props.figureUI.id);
31741
+ return this.env.model.getters.getChartType(this.chartId);
31742
+ }
31743
+ get chartId() {
31744
+ const chartId = this.env.model.getters.getChartIdFromFigureId(this.props.figureUI.id);
31745
+ if (!chartId) {
31746
+ throw new Error(`No chart found for figure ID: ${this.props.figureUI.id}`);
31747
+ }
31748
+ return chartId;
30915
31749
  }
30916
31750
  get chartComponent() {
30917
31751
  const type = this.chartType;
@@ -30928,6 +31762,7 @@ stores.inject(MyMetaStore, storeInstance);
30928
31762
  static props = {
30929
31763
  figureUI: Object,
30930
31764
  onFigureDeleted: Function,
31765
+ editFigureStyle: { type: Function, optional: true },
30931
31766
  };
30932
31767
  static components = {};
30933
31768
  // ---------------------------------------------------------------------------
@@ -30954,6 +31789,10 @@ stores.inject(MyMetaStore, storeInstance);
30954
31789
  borderWidth: 0,
30955
31790
  menuBuilder: getImageMenuActions,
30956
31791
  });
31792
+ figureRegistry.add("carousel", {
31793
+ Component: CarouselFigure,
31794
+ menuBuilder: getCarouselMenuActions,
31795
+ });
30957
31796
 
30958
31797
  // -----------------------------------------------------------------------------
30959
31798
  // STYLE
@@ -30980,6 +31819,7 @@ stores.inject(MyMetaStore, storeInstance);
30980
31819
  .o-figure-wrapper {
30981
31820
  position: absolute;
30982
31821
  box-sizing: content-box;
31822
+ pointer-events: auto;
30983
31823
 
30984
31824
  .o-fig-anchor {
30985
31825
  z-index: ${ComponentsImportance.FigureAnchor};
@@ -31038,6 +31878,7 @@ stores.inject(MyMetaStore, storeInstance);
31038
31878
  static props = {
31039
31879
  figureUI: Object,
31040
31880
  style: { type: String, optional: true },
31881
+ class: { type: String, optional: true },
31041
31882
  onFigureDeleted: { type: Function, optional: true },
31042
31883
  onMouseDown: { type: Function, optional: true },
31043
31884
  onClickAnchor: { type: Function, optional: true },
@@ -31050,6 +31891,7 @@ stores.inject(MyMetaStore, storeInstance);
31050
31891
  };
31051
31892
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
31052
31893
  figureRef = owl.useRef("figure");
31894
+ figureWrapperRef = owl.useRef("figureWrapper");
31053
31895
  menuButtonRef = owl.useRef("menuButton");
31054
31896
  borderWidth;
31055
31897
  get isSelected() {
@@ -31242,6 +32084,13 @@ stores.inject(MyMetaStore, storeInstance);
31242
32084
  .get(this.props.figureUI.tag)
31243
32085
  .menuBuilder(this.props.figureUI.id, this.props.onFigureDeleted, this.env);
31244
32086
  }
32087
+ editWrapperStyle(properties) {
32088
+ if (this.figureWrapperRef.el) {
32089
+ for (const property in properties) {
32090
+ this.figureWrapperRef.el.style.setProperty(property, properties[property] || null);
32091
+ }
32092
+ }
32093
+ }
31245
32094
  }
31246
32095
 
31247
32096
  class DelayedHoveredCellStore extends SpreadsheetStore {
@@ -38774,16 +39623,15 @@ stores.inject(MyMetaStore, storeInstance);
38774
39623
  reverseLookup.set(canonical, maxId);
38775
39624
  return maxId;
38776
39625
  }
38777
- const chartIds = [];
38778
39626
  /**
38779
39627
  * Convert a chart o-spreadsheet id to a xlsx id which
38780
39628
  * are unsigned integers (starting from 1).
38781
39629
  */
38782
- function convertChartId(chartId) {
38783
- const xlsxId = chartIds.findIndex((id) => id === chartId);
39630
+ function convertChartId(chartId, construct) {
39631
+ const xlsxId = construct.chartIds.findIndex((id) => id === chartId);
38784
39632
  if (xlsxId === -1) {
38785
- chartIds.push(chartId);
38786
- return chartIds.length;
39633
+ construct.chartIds.push(chartId);
39634
+ return construct.chartIds.length;
38787
39635
  }
38788
39636
  return xlsxId + 1;
38789
39637
  }
@@ -41178,6 +42026,8 @@ stores.inject(MyMetaStore, storeInstance);
41178
42026
  sheets: sheets,
41179
42027
  sharedStrings,
41180
42028
  externalBooks,
42029
+ chartIds: [],
42030
+ imageIds: [],
41181
42031
  };
41182
42032
  }
41183
42033
  buildXlsxFileStructure() {
@@ -41855,6 +42705,18 @@ stores.inject(MyMetaStore, storeInstance);
41855
42705
  }
41856
42706
  return data;
41857
42707
  },
42708
+ })
42709
+ .add("18.5.1", {
42710
+ migrate(data) {
42711
+ for (const sheet of data.sheets || []) {
42712
+ for (const figure of sheet.figures || []) {
42713
+ if (figure.tag === "chart") {
42714
+ figure.data.chartId = figure.id;
42715
+ }
42716
+ }
42717
+ }
42718
+ return data;
42719
+ },
41858
42720
  });
41859
42721
  function fixOverlappingFilters(data) {
41860
42722
  for (const sheet of data.sheets || []) {
@@ -42108,7 +42970,12 @@ stores.inject(MyMetaStore, storeInstance);
42108
42970
  sheet.figures?.forEach((figure) => {
42109
42971
  if (figure.tag === "chart") {
42110
42972
  // chart definition
42111
- map[figure.id] = figure.data;
42973
+ if (data.version && compareVersions(String(data.version), "18.5.1") <= 0) {
42974
+ map[figure.data.chartId] = figure.data;
42975
+ }
42976
+ else {
42977
+ map[figure.id] = figure.data;
42978
+ }
42112
42979
  }
42113
42980
  });
42114
42981
  }
@@ -42119,20 +42986,20 @@ stores.inject(MyMetaStore, storeInstance);
42119
42986
  let command = cmd;
42120
42987
  switch (cmd.type) {
42121
42988
  case "CREATE_CHART":
42122
- map[cmd.figureId] = cmd.definition;
42989
+ map[cmd.chartId] = cmd.definition;
42123
42990
  break;
42124
42991
  case "UPDATE_CHART":
42125
- if (!map[cmd.figureId]) {
42992
+ if (!map[cmd.chartId]) {
42126
42993
  /** the chart does not exist on the map, it might have been created after a duplicate sheet.
42127
42994
  * We don't have access to the definition, so we skip the command.
42128
42995
  */
42129
- console.log(`Fix chart definition: chart with id ${cmd.figureId} not found.`);
42996
+ console.log(`Fix chart definition: chart with id ${cmd.chartId} not found.`);
42130
42997
  continue;
42131
42998
  }
42132
- const definition = map[cmd.figureId];
42999
+ const definition = map[cmd.chartId];
42133
43000
  const newDefinition = { ...definition, ...cmd.definition };
42134
43001
  command = { ...cmd, definition: newDefinition };
42135
- map[cmd.figureId] = newDefinition;
43002
+ map[cmd.chartId] = newDefinition;
42136
43003
  break;
42137
43004
  }
42138
43005
  commands.push(command);
@@ -42705,6 +43572,7 @@ stores.inject(MyMetaStore, storeInstance);
42705
43572
  const result = env.model.dispatch("CREATE_CHART", {
42706
43573
  sheetId,
42707
43574
  figureId,
43575
+ chartId: figureId,
42708
43576
  col,
42709
43577
  row,
42710
43578
  offset,
@@ -42716,6 +43584,26 @@ stores.inject(MyMetaStore, storeInstance);
42716
43584
  env.openSidePanel("ChartPanel");
42717
43585
  }
42718
43586
  };
43587
+ const CREATE_CAROUSEL = (env) => {
43588
+ const getters = env.model.getters;
43589
+ const figureId = env.model.uuidGenerator.smallUuid();
43590
+ const sheetId = getters.getActiveSheetId();
43591
+ const size = { width: DEFAULT_FIGURE_WIDTH, height: DEFAULT_FIGURE_HEIGHT };
43592
+ const { col, row, offset } = centerFigurePosition(getters, size);
43593
+ const result = env.model.dispatch("CREATE_CAROUSEL", {
43594
+ sheetId,
43595
+ figureId,
43596
+ col,
43597
+ row,
43598
+ offset,
43599
+ size,
43600
+ definition: { items: [] },
43601
+ });
43602
+ if (result.isSuccessful) {
43603
+ env.model.dispatch("SELECT_FIGURE", { figureId });
43604
+ env.openSidePanel("CarouselPanel", { figureId });
43605
+ }
43606
+ };
42719
43607
  //------------------------------------------------------------------------------
42720
43608
  // Pivots
42721
43609
  //------------------------------------------------------------------------------
@@ -42752,10 +43640,18 @@ stores.inject(MyMetaStore, storeInstance);
42752
43640
  sequence: index,
42753
43641
  execute: (env) => {
42754
43642
  const zone = env.model.getters.getSelectedZone();
42755
- const table = env.model.getters.getPivot(pivotId).getExpandedTableStructure().export();
43643
+ const table = env.model.getters.getPivot(pivotId).getExpandedTableStructure();
43644
+ if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
43645
+ env.notifyUser({
43646
+ type: "warning",
43647
+ text: getPivotTooBigErrorMessage(table.numberOfCells, env.model.getters.getLocale()),
43648
+ sticky: true,
43649
+ });
43650
+ return;
43651
+ }
42756
43652
  env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
42757
43653
  pivotId,
42758
- table,
43654
+ table: table.export(),
42759
43655
  col: zone.left,
42760
43656
  row: zone.top,
42761
43657
  sheetId: env.model.getters.getActiveSheetId(),
@@ -43967,6 +44863,12 @@ stores.inject(MyMetaStore, storeInstance);
43967
44863
  isEnabled: (env) => !env.isSmall,
43968
44864
  icon: "o-spreadsheet-Icon.INSERT_CHART",
43969
44865
  };
44866
+ const insertCarousel = {
44867
+ name: _t("Carousel"),
44868
+ execute: CREATE_CAROUSEL,
44869
+ isEnabled: (env) => !env.isSmall,
44870
+ icon: "o-spreadsheet-Icon.CAROUSEL",
44871
+ };
43970
44872
  const insertPivot = {
43971
44873
  name: _t("Pivot table"),
43972
44874
  execute: CREATE_PIVOT,
@@ -44087,7 +44989,7 @@ stores.inject(MyMetaStore, storeInstance);
44087
44989
  env.openSidePanel("DataValidationEditor", {
44088
44990
  rule: localizeDataValidationRule(rule, env.model.getters.getLocale()),
44089
44991
  onExit: () => {
44090
- env.openSidePanel("DataValidation");
44992
+ env.replaceSidePanel("DataValidation", "DataValidationEditor");
44091
44993
  },
44092
44994
  });
44093
44995
  },
@@ -44823,6 +45725,9 @@ stores.inject(MyMetaStore, storeInstance);
44823
45725
  return [row, ...this.rowTreeToRows(node.children, row)];
44824
45726
  });
44825
45727
  }
45728
+ get numberOfCells() {
45729
+ return this.rows.length * this.getNumberOfDataColumns();
45730
+ }
44826
45731
  }
44827
45732
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
44828
45733
 
@@ -48057,6 +48962,7 @@ stores.inject(MyMetaStore, storeInstance);
48057
48962
  horizontalSnap: undefined,
48058
48963
  verticalSnap: undefined,
48059
48964
  cancelDnd: undefined,
48965
+ overlappingCarousel: undefined,
48060
48966
  });
48061
48967
  setup() {
48062
48968
  owl.onMounted(() => {
@@ -48079,6 +48985,7 @@ stores.inject(MyMetaStore, storeInstance);
48079
48985
  this.dnd.draggedFigure = undefined;
48080
48986
  this.dnd.horizontalSnap = undefined;
48081
48987
  this.dnd.verticalSnap = undefined;
48988
+ this.dnd.overlappingCarousel = undefined;
48082
48989
  this.dnd.cancelDnd = undefined;
48083
48990
  }
48084
48991
  });
@@ -48217,26 +49124,46 @@ stores.inject(MyMetaStore, storeInstance);
48217
49124
  const currentMousePosition = { x: ev.clientX, y: ev.clientY };
48218
49125
  const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigure, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
48219
49126
  const otherFigures = this.getOtherFigures(initialFigure.id);
48220
- const snapResult = snapForMove(getters, draggedFigure, otherFigures);
48221
- this.dnd.draggedFigure = snapResult.snappedFigure;
48222
- this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
48223
- this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
49127
+ const overlappingCarousel = this.getCarouselOverlappingChart(draggedFigure, otherFigures);
49128
+ this.dnd.overlappingCarousel = overlappingCarousel;
49129
+ if (!overlappingCarousel) {
49130
+ const snapResult = snapForMove(getters, draggedFigure, otherFigures);
49131
+ this.dnd.draggedFigure = snapResult.snappedFigure;
49132
+ this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
49133
+ this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
49134
+ }
49135
+ else {
49136
+ this.dnd.draggedFigure = draggedFigure;
49137
+ this.dnd.horizontalSnap = undefined;
49138
+ this.dnd.verticalSnap = undefined;
49139
+ }
48224
49140
  };
48225
49141
  const onMouseUp = (ev) => {
48226
49142
  if (!this.dnd.draggedFigure) {
48227
49143
  return;
48228
49144
  }
48229
49145
  const { col, row, offset } = this.env.model.getters.getPositionAnchorOffset(this.dnd.draggedFigure);
49146
+ if (!this.dnd.overlappingCarousel) {
49147
+ this.env.model.dispatch("UPDATE_FIGURE", {
49148
+ sheetId,
49149
+ figureId: figureUI.id,
49150
+ offset,
49151
+ col,
49152
+ row,
49153
+ });
49154
+ }
49155
+ else {
49156
+ this.env.model.dispatch("ADD_FIGURE_CHART_TO_CAROUSEL", {
49157
+ sheetId,
49158
+ carouselFigureId: this.dnd.overlappingCarousel.id,
49159
+ chartFigureId: figureUI.id,
49160
+ });
49161
+ this.props.onFigureDeleted();
49162
+ }
48230
49163
  this.dnd.draggedFigure = undefined;
48231
49164
  this.dnd.horizontalSnap = undefined;
48232
49165
  this.dnd.verticalSnap = undefined;
48233
- this.env.model.dispatch("UPDATE_FIGURE", {
48234
- sheetId,
48235
- figureId: figureUI.id,
48236
- offset,
48237
- col,
48238
- row,
48239
- });
49166
+ this.dnd.overlappingCarousel = undefined;
48240
49167
  };
48241
49168
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48242
49169
  }
@@ -48288,6 +49215,7 @@ stores.inject(MyMetaStore, storeInstance);
48288
49215
  this.dnd.draggedFigure = undefined;
48289
49216
  this.dnd.horizontalSnap = undefined;
48290
49217
  this.dnd.verticalSnap = undefined;
49218
+ this.dnd.overlappingCarousel = undefined;
48291
49219
  };
48292
49220
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48293
49221
  }
@@ -48304,10 +49232,15 @@ stores.inject(MyMetaStore, storeInstance);
48304
49232
  if (figureUI.id !== this.dnd.draggedFigure?.id)
48305
49233
  return "";
48306
49234
  return cssPropertiesToCss({
48307
- opacity: "0.9",
49235
+ opacity: this.dnd.overlappingCarousel?.id ? "0.6" : "0.9",
48308
49236
  cursor: "grabbing",
48309
49237
  });
48310
49238
  }
49239
+ getFigureClass(figureUI) {
49240
+ if (figureUI.id !== this.dnd.overlappingCarousel?.id)
49241
+ return "";
49242
+ return "o-add-to-carousel";
49243
+ }
48311
49244
  getSnap(snapLine) {
48312
49245
  if (!snapLine || !this.dnd.draggedFigure)
48313
49246
  return undefined;
@@ -48354,6 +49287,20 @@ stores.inject(MyMetaStore, storeInstance);
48354
49287
  });
48355
49288
  }
48356
49289
  }
49290
+ getCarouselOverlappingChart(figureUI, otherFigures) {
49291
+ if (figureUI.tag !== "chart") {
49292
+ return undefined;
49293
+ }
49294
+ const minimumOverlap = 20; // Minimum overlap in pixels to consider a carousel overlapping
49295
+ const carousels = otherFigures.filter((f) => f.tag === "carousel");
49296
+ return carousels.find((carousel) => {
49297
+ const xOverlap = Math.max(0, Math.min(figureUI.x + figureUI.width, carousel.x + carousel.width) -
49298
+ Math.max(figureUI.x, carousel.x));
49299
+ const yOverlap = Math.max(0, Math.min(figureUI.y + figureUI.height, carousel.y + carousel.height) -
49300
+ Math.max(figureUI.y, carousel.y));
49301
+ return xOverlap >= minimumOverlap && yOverlap >= minimumOverlap;
49302
+ });
49303
+ }
48357
49304
  }
48358
49305
 
48359
49306
  css /* scss */ `
@@ -51036,6 +51983,226 @@ stores.inject(MyMetaStore, storeInstance);
51036
51983
  }
51037
51984
  }
51038
51985
 
51986
+ function useAutofocus({ refName }) {
51987
+ const ref = owl.useRef(refName);
51988
+ owl.useEffect((el) => {
51989
+ el?.focus();
51990
+ }, () => [ref.el]);
51991
+ }
51992
+
51993
+ css /* scss */ `
51994
+ .o-spreadsheet {
51995
+ .os-input {
51996
+ border-width: 0 0 1px 0;
51997
+ border-color: transparent;
51998
+ outline: none;
51999
+ text-overflow: ellipsis;
52000
+ color: ${TEXT_BODY};
52001
+ }
52002
+ .os-input:hover,
52003
+ .os-input:focus {
52004
+ border-color: ${GRAY_300};
52005
+ }
52006
+ }
52007
+ `;
52008
+ class TextInput extends owl.Component {
52009
+ static template = "o-spreadsheet-TextInput";
52010
+ static props = {
52011
+ value: String,
52012
+ onChange: Function,
52013
+ class: {
52014
+ type: String,
52015
+ optional: true,
52016
+ },
52017
+ id: {
52018
+ type: String,
52019
+ optional: true,
52020
+ },
52021
+ placeholder: {
52022
+ type: String,
52023
+ optional: true,
52024
+ },
52025
+ autofocus: {
52026
+ type: Boolean,
52027
+ optional: true,
52028
+ },
52029
+ };
52030
+ inputRef = owl.useRef("input");
52031
+ setup() {
52032
+ owl.useExternalListener(window, "click", (ev) => {
52033
+ if (ev.target !== this.inputRef.el && this.inputRef.el?.value !== this.props.value) {
52034
+ this.save();
52035
+ }
52036
+ }, { capture: true });
52037
+ if (this.props.autofocus) {
52038
+ useAutofocus({ refName: "input" });
52039
+ }
52040
+ }
52041
+ onKeyDown(ev) {
52042
+ switch (ev.key) {
52043
+ case "Enter":
52044
+ this.save();
52045
+ ev.preventDefault();
52046
+ ev.stopPropagation();
52047
+ break;
52048
+ case "Escape":
52049
+ if (this.inputRef.el) {
52050
+ this.inputRef.el.value = this.props.value;
52051
+ this.inputRef.el.blur();
52052
+ }
52053
+ ev.preventDefault();
52054
+ ev.stopPropagation();
52055
+ break;
52056
+ }
52057
+ }
52058
+ save() {
52059
+ const currentValue = (this.inputRef.el?.value || "").trim();
52060
+ if (currentValue !== this.props.value) {
52061
+ this.props.onChange(currentValue);
52062
+ }
52063
+ this.inputRef.el?.blur();
52064
+ }
52065
+ onMouseDown(ev) {
52066
+ // Stop the event if the input is not focused, we handle everything in onMouseUp
52067
+ if (ev.target !== document.activeElement) {
52068
+ ev.preventDefault();
52069
+ ev.stopPropagation();
52070
+ }
52071
+ }
52072
+ onMouseUp(ev) {
52073
+ const target = ev.target;
52074
+ if (target !== document.activeElement) {
52075
+ target.focus();
52076
+ target.select();
52077
+ ev.preventDefault();
52078
+ ev.stopPropagation();
52079
+ }
52080
+ }
52081
+ }
52082
+
52083
+ class CarouselPanel extends owl.Component {
52084
+ static template = "o-spreadsheet-CarouselPanel";
52085
+ static props = { onCloseSidePanel: Function, figureId: String };
52086
+ static components = { TextInput };
52087
+ dragAndDrop = useDragAndDropListItems();
52088
+ previewListRef = owl.useRef("previewList");
52089
+ setup() {
52090
+ let lastCarouselItems = [...this.carouselItems];
52091
+ owl.onWillUpdateProps(() => {
52092
+ if (!deepEquals(this.carouselItems, lastCarouselItems)) {
52093
+ this.dragAndDrop.cancel();
52094
+ }
52095
+ lastCarouselItems = [...this.carouselItems];
52096
+ });
52097
+ }
52098
+ get carouselItems() {
52099
+ return this.env.model.getters.getCarousel(this.props.figureId).items;
52100
+ }
52101
+ getPreviewDivStyle(item) {
52102
+ return this.dragAndDrop.itemsStyle[this.getItemId(item)] || "";
52103
+ }
52104
+ getItemId(item) {
52105
+ return item.type === "chart" ? item.chartId : "transparent-carousel";
52106
+ }
52107
+ addNewChartToCarousel() {
52108
+ this.env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
52109
+ figureId: this.props.figureId,
52110
+ sheetId: this.env.model.getters.getActiveSheetId(),
52111
+ });
52112
+ }
52113
+ get hasDataView() {
52114
+ return this.carouselItems.some((item) => item.type === "carouselDataView");
52115
+ }
52116
+ addDataViewToCarousel() {
52117
+ const carousel = this.env.model.getters.getCarousel(this.props.figureId);
52118
+ this.env.model.dispatch("UPDATE_CAROUSEL", {
52119
+ figureId: this.props.figureId,
52120
+ sheetId: this.env.model.getters.getActiveSheetId(),
52121
+ definition: { items: [...carousel.items, { type: "carouselDataView" }] },
52122
+ });
52123
+ }
52124
+ activateCarouselItem(item) {
52125
+ this.env.model.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
52126
+ figureId: this.props.figureId,
52127
+ sheetId: this.env.model.getters.getActiveSheetId(),
52128
+ item,
52129
+ });
52130
+ }
52131
+ editCarouselItem(item) {
52132
+ if (item.type === "chart") {
52133
+ this.activateCarouselItem(item);
52134
+ this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureId });
52135
+ this.env.openSidePanel("ChartPanel", { chartId: item.chartId });
52136
+ }
52137
+ }
52138
+ renameCarouselItem(item, newName) {
52139
+ const trimmedName = newName.trim();
52140
+ if (!trimmedName || trimmedName === this.getItemTitle(item).toString()) {
52141
+ return;
52142
+ }
52143
+ const items = [...this.carouselItems];
52144
+ const itemIndex = this.carouselItems.findIndex((itm) => deepEquals(itm, item));
52145
+ if (itemIndex !== -1) {
52146
+ items[itemIndex] = { ...item, title: trimmedName };
52147
+ this.env.model.dispatch("UPDATE_CAROUSEL", {
52148
+ figureId: this.props.figureId,
52149
+ sheetId: this.env.model.getters.getActiveSheetId(),
52150
+ definition: { items },
52151
+ });
52152
+ }
52153
+ }
52154
+ deleteCarouselItem(item) {
52155
+ const carousel = this.env.model.getters.getCarousel(this.props.figureId);
52156
+ const items = carousel.items.filter((itm) => !deepEquals(itm, item));
52157
+ this.env.model.dispatch("UPDATE_CAROUSEL", {
52158
+ figureId: this.props.figureId,
52159
+ sheetId: this.env.model.getters.getActiveSheetId(),
52160
+ definition: { items },
52161
+ });
52162
+ }
52163
+ onDragHandleMouseDown(item, event) {
52164
+ if (event.button !== 0)
52165
+ return;
52166
+ const previewRects = Array.from(this.previewListRef.el.children).map((previewEl) => getBoundingRectAsPOJO(previewEl));
52167
+ const items = this.carouselItems.map((item, index) => ({
52168
+ id: this.getItemId(item),
52169
+ size: previewRects[index].height,
52170
+ position: previewRects[index].y,
52171
+ }));
52172
+ this.dragAndDrop.start("vertical", {
52173
+ draggedItemId: this.getItemId(item),
52174
+ initialMousePosition: event.clientY,
52175
+ items: items,
52176
+ scrollableContainerEl: this.previewListRef.el,
52177
+ onDragEnd: (itemId, finalIndex) => this.onDragEnd(item, finalIndex),
52178
+ });
52179
+ }
52180
+ onDragEnd(item, finalIndex) {
52181
+ const originalIndex = this.carouselItems.findIndex((itm) => deepEquals(itm, item));
52182
+ if (originalIndex === -1 || originalIndex === finalIndex) {
52183
+ return;
52184
+ }
52185
+ const carousel = this.env.model.getters.getCarousel(this.props.figureId);
52186
+ const items = [...carousel.items];
52187
+ items.splice(originalIndex, 1);
52188
+ items.splice(finalIndex, 0, item);
52189
+ this.env.model.dispatch("UPDATE_CAROUSEL", {
52190
+ figureId: this.props.figureId,
52191
+ sheetId: this.env.model.getters.getActiveSheetId(),
52192
+ definition: { items },
52193
+ });
52194
+ }
52195
+ getItemTitle(item) {
52196
+ return getCarouselItemTitle(this.env.model.getters, item);
52197
+ }
52198
+ getItemPreview(item) {
52199
+ return getCarouselItemPreview(this.env.model.getters, item);
52200
+ }
52201
+ get carouselAddChartInfoMessage() {
52202
+ return _t("Add a chart to the carousel. You can also add a chart by dragging and dropping it over the carousel figure.");
52203
+ }
52204
+ }
52205
+
51039
52206
  class ChartDataSeries extends owl.Component {
51040
52207
  static template = "o-spreadsheet.ChartDataSeries";
51041
52208
  static components = { SelectionInput, Section };
@@ -51102,7 +52269,7 @@ stores.inject(MyMetaStore, storeInstance);
51102
52269
  ChartErrorSection,
51103
52270
  };
51104
52271
  static props = {
51105
- figureId: String,
52272
+ chartId: String,
51106
52273
  definition: Object,
51107
52274
  updateChart: Function,
51108
52275
  canUpdateChart: Function,
@@ -51160,7 +52327,7 @@ stores.inject(MyMetaStore, storeInstance);
51160
52327
  ];
51161
52328
  }
51162
52329
  onUpdateDataSetsHaveTitle(dataSetsHaveTitle) {
51163
- this.props.updateChart(this.props.figureId, {
52330
+ this.props.updateChart(this.props.chartId, {
51164
52331
  dataSetsHaveTitle,
51165
52332
  });
51166
52333
  }
@@ -51232,7 +52399,7 @@ stores.inject(MyMetaStore, storeInstance);
51232
52399
  return;
51233
52400
  }
51234
52401
  const labelRange = dataSets.length > 1 ? dataSets.shift().dataRange : "";
51235
- this.props.updateChart(this.props.figureId, {
52402
+ this.props.updateChart(this.props.chartId, {
51236
52403
  labelRange,
51237
52404
  dataSets,
51238
52405
  });
@@ -51249,7 +52416,7 @@ stores.inject(MyMetaStore, storeInstance);
51249
52416
  ...this.dataSets?.[i],
51250
52417
  dataRange,
51251
52418
  }));
51252
- this.state.datasetDispatchResult = this.props.canUpdateChart(this.props.figureId, {
52419
+ this.state.datasetDispatchResult = this.props.canUpdateChart(this.props.chartId, {
51253
52420
  dataSets: this.dataSets,
51254
52421
  });
51255
52422
  }
@@ -51261,7 +52428,7 @@ stores.inject(MyMetaStore, storeInstance);
51261
52428
  backgroundColor: colors[i],
51262
52429
  ...this.dataSets[i],
51263
52430
  }));
51264
- this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
52431
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.chartId, {
51265
52432
  dataSets: this.dataSets,
51266
52433
  });
51267
52434
  }
@@ -51274,18 +52441,18 @@ stores.inject(MyMetaStore, storeInstance);
51274
52441
  ...ds,
51275
52442
  }))
51276
52443
  .filter((_, i) => i !== index);
51277
- this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
52444
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.chartId, {
51278
52445
  dataSets: this.dataSets,
51279
52446
  });
51280
52447
  }
51281
52448
  onDataSeriesConfirmed() {
51282
52449
  this.dataSets = this.splitRanges;
51283
52450
  this.datasetOrientation = this.computeDatasetOrientation();
51284
- this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
52451
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.chartId, {
51285
52452
  dataSets: this.dataSets,
51286
52453
  });
51287
52454
  if (this.state.datasetDispatchResult.isSuccessful) {
51288
- this.dataSets = this.env.model.getters.getChartDefinition(this.props.figureId).dataSets;
52455
+ this.dataSets = this.env.model.getters.getChartDefinition(this.props.chartId).dataSets;
51289
52456
  }
51290
52457
  }
51291
52458
  get splitRanges() {
@@ -51376,12 +52543,12 @@ stores.inject(MyMetaStore, storeInstance);
51376
52543
  */
51377
52544
  onLabelRangeChanged(ranges) {
51378
52545
  this.labelRange = ranges[0];
51379
- this.state.labelsDispatchResult = this.props.canUpdateChart(this.props.figureId, {
52546
+ this.state.labelsDispatchResult = this.props.canUpdateChart(this.props.chartId, {
51380
52547
  labelRange: this.labelRange,
51381
52548
  });
51382
52549
  }
51383
52550
  onLabelRangeConfirmed() {
51384
- this.state.labelsDispatchResult = this.props.updateChart(this.props.figureId, {
52551
+ this.state.labelsDispatchResult = this.props.updateChart(this.props.chartId, {
51385
52552
  labelRange: this.labelRange,
51386
52553
  });
51387
52554
  }
@@ -51389,7 +52556,7 @@ stores.inject(MyMetaStore, storeInstance);
51389
52556
  return this.labelRange || "";
51390
52557
  }
51391
52558
  onUpdateAggregated(aggregated) {
51392
- this.props.updateChart(this.props.figureId, {
52559
+ this.props.updateChart(this.props.chartId, {
51393
52560
  aggregated,
51394
52561
  });
51395
52562
  }
@@ -51421,7 +52588,8 @@ stores.inject(MyMetaStore, storeInstance);
51421
52588
  }
51422
52589
  const zonesBySheetName = {};
51423
52590
  const transposedDatasets = [];
51424
- const figureSheetId = getters.getFigureSheetId(this.props.figureId);
52591
+ const figureId = getters.getFigureIdFromChartId(this.props.chartId);
52592
+ const figureSheetId = getters.getFigureSheetId(figureId);
51425
52593
  let name = getters.getActiveSheet().name;
51426
52594
  if (figureSheetId) {
51427
52595
  name = getters.getSheet(figureSheetId).name;
@@ -51481,7 +52649,7 @@ stores.inject(MyMetaStore, storeInstance);
51481
52649
  : this.chartTerms.StackedColumnChart;
51482
52650
  }
51483
52651
  onUpdateStacked(stacked) {
51484
- this.props.updateChart(this.props.figureId, {
52652
+ this.props.updateChart(this.props.chartId, {
51485
52653
  stacked,
51486
52654
  });
51487
52655
  }
@@ -51975,7 +53143,7 @@ stores.inject(MyMetaStore, storeInstance);
51975
53143
  class AxisDesignEditor extends owl.Component {
51976
53144
  static template = "o-spreadsheet-AxisDesignEditor";
51977
53145
  static components = { Section, ChartTitle, BadgeSelection };
51978
- static props = { figureId: String, definition: Object, updateChart: Function, axesList: Array };
53146
+ static props = { chartId: String, definition: Object, updateChart: Function, axesList: Array };
51979
53147
  state = owl.useState({ currentAxis: "x" });
51980
53148
  defaultFontSize = CHART_AXIS_TITLE_FONT_SIZE;
51981
53149
  get axisTitleStyle() {
@@ -52000,7 +53168,7 @@ stores.inject(MyMetaStore, storeInstance);
52000
53168
  text,
52001
53169
  },
52002
53170
  };
52003
- this.props.updateChart(this.props.figureId, { axesDesign });
53171
+ this.props.updateChart(this.props.chartId, { axesDesign });
52004
53172
  }
52005
53173
  updateAxisTitleStyle(style) {
52006
53174
  const axesDesign = deepCopy(this.props.definition.axesDesign) ?? {};
@@ -52008,7 +53176,7 @@ stores.inject(MyMetaStore, storeInstance);
52008
53176
  ...axesDesign[this.state.currentAxis],
52009
53177
  title: style,
52010
53178
  };
52011
- this.props.updateChart(this.props.figureId, { axesDesign });
53179
+ this.props.updateChart(this.props.chartId, { axesDesign });
52012
53180
  }
52013
53181
  }
52014
53182
 
@@ -52061,7 +53229,7 @@ stores.inject(MyMetaStore, storeInstance);
52061
53229
  RadioSelection,
52062
53230
  };
52063
53231
  static props = {
52064
- figureId: String,
53232
+ chartId: String,
52065
53233
  definition: Object,
52066
53234
  updateChart: Function,
52067
53235
  canUpdateChart: Function,
@@ -52085,17 +53253,17 @@ stores.inject(MyMetaStore, storeInstance);
52085
53253
  this.state.activeTool = isOpen ? "" : tool;
52086
53254
  }
52087
53255
  updateBackgroundColor(color) {
52088
- this.props.updateChart(this.props.figureId, {
53256
+ this.props.updateChart(this.props.chartId, {
52089
53257
  background: color,
52090
53258
  });
52091
53259
  }
52092
53260
  updateTitle(newTitle) {
52093
53261
  const title = { ...this.title, text: newTitle };
52094
- this.props.updateChart(this.props.figureId, { title });
53262
+ this.props.updateChart(this.props.chartId, { title });
52095
53263
  }
52096
53264
  updateChartTitleStyle(style) {
52097
53265
  const title = { ...this.title, ...style };
52098
- this.props.updateChart(this.props.figureId, { title });
53266
+ this.props.updateChart(this.props.chartId, { title });
52099
53267
  this.state.activeTool = "";
52100
53268
  }
52101
53269
  }
@@ -52106,13 +53274,13 @@ stores.inject(MyMetaStore, storeInstance);
52106
53274
  Section,
52107
53275
  };
52108
53276
  static props = {
52109
- figureId: String,
53277
+ chartId: String,
52110
53278
  definition: Object,
52111
53279
  updateChart: Function,
52112
53280
  canUpdateChart: Function,
52113
53281
  };
52114
53282
  updateLegendPosition(ev) {
52115
- this.props.updateChart(this.props.figureId, {
53283
+ this.props.updateChart(this.props.chartId, {
52116
53284
  legendPosition: ev.target.value,
52117
53285
  });
52118
53286
  }
@@ -52126,7 +53294,7 @@ stores.inject(MyMetaStore, storeInstance);
52126
53294
  RoundColorPicker,
52127
53295
  };
52128
53296
  static props = {
52129
- figureId: String,
53297
+ chartId: String,
52130
53298
  definition: Object,
52131
53299
  updateChart: Function,
52132
53300
  canUpdateChart: Function,
@@ -52134,7 +53302,7 @@ stores.inject(MyMetaStore, storeInstance);
52134
53302
  };
52135
53303
  state = owl.useState({ index: 0 });
52136
53304
  getDataSeries() {
52137
- const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
53305
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
52138
53306
  if (!runtime || !("chartJsConfig" in runtime)) {
52139
53307
  return [];
52140
53308
  }
@@ -52153,7 +53321,7 @@ stores.inject(MyMetaStore, storeInstance);
52153
53321
  ...dataSets[this.state.index],
52154
53322
  backgroundColor: color,
52155
53323
  };
52156
- this.props.updateChart(this.props.figureId, { dataSets });
53324
+ this.props.updateChart(this.props.chartId, { dataSets });
52157
53325
  }
52158
53326
  getDataSeriesColor() {
52159
53327
  const dataSets = this.props.definition.dataSets;
@@ -52173,7 +53341,7 @@ stores.inject(MyMetaStore, storeInstance);
52173
53341
  ...dataSets[this.state.index],
52174
53342
  label,
52175
53343
  };
52176
- this.props.updateChart(this.props.figureId, { dataSets });
53344
+ this.props.updateChart(this.props.chartId, { dataSets });
52177
53345
  }
52178
53346
  getDataSeriesLabel() {
52179
53347
  const dataSets = this.props.definition.dataSets;
@@ -52191,7 +53359,7 @@ stores.inject(MyMetaStore, storeInstance);
52191
53359
  RoundColorPicker,
52192
53360
  };
52193
53361
  static props = {
52194
- figureId: String,
53362
+ chartId: String,
52195
53363
  definition: Object,
52196
53364
  canUpdateChart: Function,
52197
53365
  updateChart: Function,
@@ -52207,7 +53375,7 @@ stores.inject(MyMetaStore, storeInstance);
52207
53375
  ...dataSets[index],
52208
53376
  yAxisId: axis === "left" ? "y" : "y1",
52209
53377
  };
52210
- this.props.updateChart(this.props.figureId, { dataSets });
53378
+ this.props.updateChart(this.props.chartId, { dataSets });
52211
53379
  }
52212
53380
  getDataSerieAxis(index) {
52213
53381
  const dataSets = this.props.definition.dataSets;
@@ -52233,7 +53401,7 @@ stores.inject(MyMetaStore, storeInstance);
52233
53401
  display,
52234
53402
  },
52235
53403
  };
52236
- this.props.updateChart(this.props.figureId, { dataSets });
53404
+ this.props.updateChart(this.props.chartId, { dataSets });
52237
53405
  }
52238
53406
  getTrendLineConfiguration(index) {
52239
53407
  const dataSets = this.props.definition.dataSets;
@@ -52274,7 +53442,7 @@ stores.inject(MyMetaStore, storeInstance);
52274
53442
  this.updateTrendLineValue(index, { order: parseInt(element.value) });
52275
53443
  }
52276
53444
  getMaxPolynomialDegree(index) {
52277
- const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
53445
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
52278
53446
  return Math.min(10, runtime.chartJsConfig.data.datasets[index].data.length - 1);
52279
53447
  }
52280
53448
  get defaultWindowSize() {
@@ -52316,7 +53484,7 @@ stores.inject(MyMetaStore, storeInstance);
52316
53484
  ...config,
52317
53485
  },
52318
53486
  };
52319
- this.props.updateChart(this.props.figureId, { dataSets });
53487
+ this.props.updateChart(this.props.chartId, { dataSets });
52320
53488
  }
52321
53489
  }
52322
53490
 
@@ -52326,7 +53494,7 @@ stores.inject(MyMetaStore, storeInstance);
52326
53494
  Checkbox,
52327
53495
  };
52328
53496
  static props = {
52329
- figureId: String,
53497
+ chartId: String,
52330
53498
  definition: Object,
52331
53499
  updateChart: Function,
52332
53500
  canUpdateChart: Function,
@@ -52346,7 +53514,7 @@ stores.inject(MyMetaStore, storeInstance);
52346
53514
  ChartShowValues,
52347
53515
  };
52348
53516
  static props = {
52349
- figureId: String,
53517
+ chartId: String,
52350
53518
  definition: Object,
52351
53519
  canUpdateChart: Function,
52352
53520
  updateChart: Function,
@@ -52370,17 +53538,30 @@ stores.inject(MyMetaStore, storeInstance);
52370
53538
  Checkbox,
52371
53539
  };
52372
53540
  static props = {
52373
- figureId: String,
53541
+ chartId: String,
52374
53542
  definition: Object,
52375
53543
  updateChart: Function,
52376
53544
  canUpdateChart: Function,
52377
53545
  };
52378
53546
  }
52379
53547
 
52380
- class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
52381
- static template = "o-spreadsheet-ComboChartDesignPanel";
53548
+ class GenericZoomableChartDesignPanel extends ChartWithAxisDesignPanel {
53549
+ static template = "o-spreadsheet-GenericZoomableChartDesignPanel";
52382
53550
  static components = {
52383
53551
  ...ChartWithAxisDesignPanel.components,
53552
+ Checkbox,
53553
+ };
53554
+ onToggleZoom(zoomable) {
53555
+ this.props.updateChart(this.props.chartId, {
53556
+ zoomable,
53557
+ });
53558
+ }
53559
+ }
53560
+
53561
+ class ComboChartDesignPanel extends GenericZoomableChartDesignPanel {
53562
+ static template = "o-spreadsheet-ComboChartDesignPanel";
53563
+ static components = {
53564
+ ...GenericZoomableChartDesignPanel.components,
52384
53565
  ChartShowDataMarkers,
52385
53566
  RadioSelection,
52386
53567
  };
@@ -52397,7 +53578,7 @@ stores.inject(MyMetaStore, storeInstance);
52397
53578
  ...dataSets[index],
52398
53579
  type,
52399
53580
  };
52400
- this.props.updateChart(this.props.figureId, { dataSets });
53581
+ this.props.updateChart(this.props.chartId, { dataSets });
52401
53582
  }
52402
53583
  getDataSeriesType(index) {
52403
53584
  const dataSets = this.props.definition.dataSets;
@@ -52433,7 +53614,7 @@ stores.inject(MyMetaStore, storeInstance);
52433
53614
  ];
52434
53615
  }
52435
53616
  onUpdateCumulative(cumulative) {
52436
- this.props.updateChart(this.props.figureId, {
53617
+ this.props.updateChart(this.props.chartId, {
52437
53618
  cumulative,
52438
53619
  });
52439
53620
  }
@@ -52449,13 +53630,13 @@ stores.inject(MyMetaStore, storeInstance);
52449
53630
  Section,
52450
53631
  };
52451
53632
  static props = {
52452
- figureId: String,
53633
+ chartId: String,
52453
53634
  definition: Object,
52454
53635
  updateChart: Function,
52455
53636
  canUpdateChart: Function,
52456
53637
  };
52457
53638
  getFunnelColorItems() {
52458
- const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
53639
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
52459
53640
  const labels = (runtime.chartJsConfig.data.labels || []);
52460
53641
  const colors = getFunnelLabelColors(labels, this.props.definition.funnelColors);
52461
53642
  return labels.map((label, index) => ({
@@ -52465,7 +53646,7 @@ stores.inject(MyMetaStore, storeInstance);
52465
53646
  }
52466
53647
  updateFunnelItemColor(index, color) {
52467
53648
  const funnelColors = replaceItemAtIndex(this.props.definition.funnelColors || [], color, index);
52468
- this.props.updateChart(this.props.figureId, { funnelColors });
53649
+ this.props.updateChart(this.props.chartId, { funnelColors });
52469
53650
  }
52470
53651
  }
52471
53652
 
@@ -52473,7 +53654,7 @@ stores.inject(MyMetaStore, storeInstance);
52473
53654
  static template = "o-spreadsheet-GaugeChartConfigPanel";
52474
53655
  static components = { ChartErrorSection, ChartDataSeries };
52475
53656
  static props = {
52476
- figureId: String,
53657
+ chartId: String,
52477
53658
  definition: Object,
52478
53659
  updateChart: Function,
52479
53660
  canUpdateChart: Function,
@@ -52491,12 +53672,12 @@ stores.inject(MyMetaStore, storeInstance);
52491
53672
  }
52492
53673
  onDataRangeChanged(ranges) {
52493
53674
  this.dataRange = ranges[0];
52494
- this.state.dataRangeDispatchResult = this.props.canUpdateChart(this.props.figureId, {
53675
+ this.state.dataRangeDispatchResult = this.props.canUpdateChart(this.props.chartId, {
52495
53676
  dataRange: this.dataRange,
52496
53677
  });
52497
53678
  }
52498
53679
  updateDataRange() {
52499
- this.state.dataRangeDispatchResult = this.props.updateChart(this.props.figureId, {
53680
+ this.state.dataRangeDispatchResult = this.props.updateChart(this.props.chartId, {
52500
53681
  dataRange: this.dataRange,
52501
53682
  });
52502
53683
  }
@@ -52555,7 +53736,7 @@ stores.inject(MyMetaStore, storeInstance);
52555
53736
  StandaloneComposer,
52556
53737
  };
52557
53738
  static props = {
52558
- figureId: String,
53739
+ chartId: String,
52559
53740
  definition: Object,
52560
53741
  updateChart: Function,
52561
53742
  canUpdateChart: { type: Function, optional: true },
@@ -52596,7 +53777,7 @@ stores.inject(MyMetaStore, storeInstance);
52596
53777
  updateSectionRule(sectionRule) {
52597
53778
  this.state.sectionRuleCancelledReasons = [];
52598
53779
  this.state.sectionRuleCancelledReasons.push(...this.checkSectionRuleFormulasAreValid(this.state.sectionRule));
52599
- const dispatchResult = this.props.updateChart(this.props.figureId, {
53780
+ const dispatchResult = this.props.updateChart(this.props.chartId, {
52600
53781
  sectionRule,
52601
53782
  });
52602
53783
  if (dispatchResult.isSuccessful) {
@@ -52659,9 +53840,9 @@ stores.inject(MyMetaStore, storeInstance);
52659
53840
  return tryToNumber(toScalar(evaluatedValue), locale) !== undefined;
52660
53841
  }
52661
53842
  get sheetId() {
52662
- const chart = this.env.model.getters.getChart(this.props.figureId);
53843
+ const chart = this.env.model.getters.getChart(this.props.chartId);
52663
53844
  if (!chart) {
52664
- throw new Error("Chart not found with id " + this.props.figureId);
53845
+ throw new Error("Chart not found with id " + this.props.chartId);
52665
53846
  }
52666
53847
  return chart.sheetId;
52667
53848
  }
@@ -52671,13 +53852,13 @@ stores.inject(MyMetaStore, storeInstance);
52671
53852
  static template = "o-spreadsheet-GeoChartRegionSelectSection";
52672
53853
  static components = { Section };
52673
53854
  static props = {
52674
- figureId: String,
53855
+ chartId: String,
52675
53856
  definition: Object,
52676
53857
  updateChart: Function,
52677
53858
  };
52678
53859
  updateSelectedRegion(ev) {
52679
53860
  const value = ev.target.value;
52680
- this.props.updateChart(this.props.figureId, { region: value });
53861
+ this.props.updateChart(this.props.chartId, { region: value });
52681
53862
  }
52682
53863
  get availableRegions() {
52683
53864
  return this.env.model.getters.getGeoChartAvailableRegions();
@@ -52727,14 +53908,14 @@ stores.inject(MyMetaStore, storeInstance);
52727
53908
  : this.updateColorScale(value);
52728
53909
  }
52729
53910
  updateColorScale(colorScale) {
52730
- this.props.updateChart(this.props.figureId, { colorScale });
53911
+ this.props.updateChart(this.props.chartId, { colorScale });
52731
53912
  }
52732
53913
  updateMissingValueColor(color) {
52733
- this.props.updateChart(this.props.figureId, { missingValueColor: color });
53914
+ this.props.updateChart(this.props.chartId, { missingValueColor: color });
52734
53915
  }
52735
53916
  updateLegendPosition(ev) {
52736
53917
  const value = ev.target.value;
52737
- this.props.updateChart(this.props.figureId, { legendPosition: value });
53918
+ this.props.updateChart(this.props.chartId, { legendPosition: value });
52738
53919
  }
52739
53920
  get selectedColorScale() {
52740
53921
  return typeof this.props.definition.colorScale === "object"
@@ -52783,7 +53964,7 @@ stores.inject(MyMetaStore, storeInstance);
52783
53964
  class LineConfigPanel extends GenericChartConfigPanel {
52784
53965
  static template = "o-spreadsheet-LineConfigPanel";
52785
53966
  get canTreatLabelsAsText() {
52786
- const chart = this.env.model.getters.getChart(this.props.figureId);
53967
+ const chart = this.env.model.getters.getChart(this.props.chartId);
52787
53968
  if (chart && chart instanceof LineChart) {
52788
53969
  return canChartParseLabels(chart.getDefinition(), chart.dataSets, chart.labelRange, this.env.model.getters);
52789
53970
  }
@@ -52808,26 +53989,26 @@ stores.inject(MyMetaStore, storeInstance);
52808
53989
  return options;
52809
53990
  }
52810
53991
  onUpdateLabelsAsText(labelsAsText) {
52811
- this.props.updateChart(this.props.figureId, {
53992
+ this.props.updateChart(this.props.chartId, {
52812
53993
  labelsAsText,
52813
53994
  });
52814
53995
  }
52815
53996
  onUpdateStacked(stacked) {
52816
- this.props.updateChart(this.props.figureId, {
53997
+ this.props.updateChart(this.props.chartId, {
52817
53998
  stacked,
52818
53999
  });
52819
54000
  }
52820
54001
  onUpdateCumulative(cumulative) {
52821
- this.props.updateChart(this.props.figureId, {
54002
+ this.props.updateChart(this.props.chartId, {
52822
54003
  cumulative,
52823
54004
  });
52824
54005
  }
52825
54006
  }
52826
54007
 
52827
- class LineChartDesignPanel extends ChartWithAxisDesignPanel {
54008
+ class LineChartDesignPanel extends GenericZoomableChartDesignPanel {
52828
54009
  static template = "o-spreadsheet-LineChartDesignPanel";
52829
54010
  static components = {
52830
- ...ChartWithAxisDesignPanel.components,
54011
+ ...GenericZoomableChartDesignPanel.components,
52831
54012
  ChartShowDataMarkers,
52832
54013
  };
52833
54014
  }
@@ -52855,13 +54036,13 @@ stores.inject(MyMetaStore, storeInstance);
52855
54036
  PieHoleSize,
52856
54037
  };
52857
54038
  static props = {
52858
- figureId: String,
54039
+ chartId: String,
52859
54040
  definition: Object,
52860
54041
  updateChart: Function,
52861
54042
  canUpdateChart: { type: Function, optional: true },
52862
54043
  };
52863
54044
  onPieHoleSizeChange(pieHolePercentage) {
52864
- this.props.updateChart(this.props.figureId, {
54045
+ this.props.updateChart(this.props.chartId, {
52865
54046
  ...this.props.definition,
52866
54047
  pieHolePercentage,
52867
54048
  });
@@ -52882,7 +54063,7 @@ stores.inject(MyMetaStore, storeInstance);
52882
54063
  ChartShowDataMarkers,
52883
54064
  };
52884
54065
  static props = {
52885
- figureId: String,
54066
+ chartId: String,
52886
54067
  definition: Object,
52887
54068
  canUpdateChart: Function,
52888
54069
  updateChart: Function,
@@ -52892,14 +54073,14 @@ stores.inject(MyMetaStore, storeInstance);
52892
54073
  class ScatterConfigPanel extends GenericChartConfigPanel {
52893
54074
  static template = "o-spreadsheet-ScatterConfigPanel";
52894
54075
  get canTreatLabelsAsText() {
52895
- const chart = this.env.model.getters.getChart(this.props.figureId);
54076
+ const chart = this.env.model.getters.getChart(this.props.chartId);
52896
54077
  if (chart && chart instanceof ScatterChart) {
52897
54078
  return canChartParseLabels(chart.getDefinition(), chart.dataSets, chart.labelRange, this.env.model.getters);
52898
54079
  }
52899
54080
  return false;
52900
54081
  }
52901
54082
  onUpdateLabelsAsText(labelsAsText) {
52902
- this.props.updateChart(this.props.figureId, {
54083
+ this.props.updateChart(this.props.chartId, {
52903
54084
  labelsAsText,
52904
54085
  });
52905
54086
  }
@@ -52921,7 +54102,7 @@ stores.inject(MyMetaStore, storeInstance);
52921
54102
  static template = "o-spreadsheet-ScorecardChartConfigPanel";
52922
54103
  static components = { SelectionInput, ChartErrorSection, Section };
52923
54104
  static props = {
52924
- figureId: String,
54105
+ chartId: String,
52925
54106
  definition: Object,
52926
54107
  updateChart: Function,
52927
54108
  canUpdateChart: Function,
@@ -52947,12 +54128,12 @@ stores.inject(MyMetaStore, storeInstance);
52947
54128
  }
52948
54129
  onKeyValueRangeChanged(ranges) {
52949
54130
  this.keyValue = ranges[0];
52950
- this.state.keyValueDispatchResult = this.props.canUpdateChart(this.props.figureId, {
54131
+ this.state.keyValueDispatchResult = this.props.canUpdateChart(this.props.chartId, {
52951
54132
  keyValue: this.keyValue,
52952
54133
  });
52953
54134
  }
52954
54135
  updateKeyValueRange() {
52955
- this.state.keyValueDispatchResult = this.props.updateChart(this.props.figureId, {
54136
+ this.state.keyValueDispatchResult = this.props.updateChart(this.props.chartId, {
52956
54137
  keyValue: this.keyValue,
52957
54138
  });
52958
54139
  }
@@ -52961,12 +54142,12 @@ stores.inject(MyMetaStore, storeInstance);
52961
54142
  }
52962
54143
  onBaselineRangeChanged(ranges) {
52963
54144
  this.baseline = ranges[0];
52964
- this.state.baselineDispatchResult = this.props.canUpdateChart(this.props.figureId, {
54145
+ this.state.baselineDispatchResult = this.props.canUpdateChart(this.props.chartId, {
52965
54146
  baseline: this.baseline,
52966
54147
  });
52967
54148
  }
52968
54149
  updateBaselineRange() {
52969
- this.state.baselineDispatchResult = this.props.updateChart(this.props.figureId, {
54150
+ this.state.baselineDispatchResult = this.props.updateChart(this.props.chartId, {
52970
54151
  baseline: this.baseline,
52971
54152
  });
52972
54153
  }
@@ -52974,7 +54155,7 @@ stores.inject(MyMetaStore, storeInstance);
52974
54155
  return this.baseline || "";
52975
54156
  }
52976
54157
  updateBaselineMode(ev) {
52977
- this.props.updateChart(this.props.figureId, { baselineMode: ev.target.value });
54158
+ this.props.updateChart(this.props.chartId, { baselineMode: ev.target.value });
52978
54159
  }
52979
54160
  }
52980
54161
 
@@ -52989,7 +54170,7 @@ stores.inject(MyMetaStore, storeInstance);
52989
54170
  ChartTitle,
52990
54171
  };
52991
54172
  static props = {
52992
- figureId: String,
54173
+ chartId: String,
52993
54174
  definition: Object,
52994
54175
  updateChart: Function,
52995
54176
  canUpdateChart: { type: Function, optional: true },
@@ -53006,7 +54187,7 @@ stores.inject(MyMetaStore, storeInstance);
53006
54187
  return SCORECARD_CHART_TITLE_FONT_SIZE;
53007
54188
  }
53008
54189
  updateHumanizeNumbers(humanize) {
53009
- this.props.updateChart(this.props.figureId, { humanize });
54190
+ this.props.updateChart(this.props.chartId, { humanize });
53010
54191
  }
53011
54192
  translate(term) {
53012
54193
  return _t(term);
@@ -53014,13 +54195,13 @@ stores.inject(MyMetaStore, storeInstance);
53014
54195
  setColor(color, colorPickerId) {
53015
54196
  switch (colorPickerId) {
53016
54197
  case "backgroundColor":
53017
- this.props.updateChart(this.props.figureId, { background: color });
54198
+ this.props.updateChart(this.props.chartId, { background: color });
53018
54199
  break;
53019
54200
  case "baselineColorDown":
53020
- this.props.updateChart(this.props.figureId, { baselineColorDown: color });
54201
+ this.props.updateChart(this.props.chartId, { baselineColorDown: color });
53021
54202
  break;
53022
54203
  case "baselineColorUp":
53023
- this.props.updateChart(this.props.figureId, { baselineColorUp: color });
54204
+ this.props.updateChart(this.props.chartId, { baselineColorUp: color });
53024
54205
  break;
53025
54206
  }
53026
54207
  }
@@ -53039,22 +54220,22 @@ stores.inject(MyMetaStore, storeInstance);
53039
54220
  };
53040
54221
  }
53041
54222
  setKeyText(text) {
53042
- this.props.updateChart(this.props.figureId, {
54223
+ this.props.updateChart(this.props.chartId, {
53043
54224
  keyDescr: { ...this.props.definition.keyDescr, text },
53044
54225
  });
53045
54226
  }
53046
54227
  updateKeyStyle(style) {
53047
54228
  const keyDescr = { ...this.keyStyle, ...style };
53048
- this.props.updateChart(this.props.figureId, { keyDescr });
54229
+ this.props.updateChart(this.props.chartId, { keyDescr });
53049
54230
  }
53050
54231
  setBaselineText(text) {
53051
- this.props.updateChart(this.props.figureId, {
54232
+ this.props.updateChart(this.props.chartId, {
53052
54233
  baselineDescr: { ...this.props.definition.baselineDescr, text },
53053
54234
  });
53054
54235
  }
53055
54236
  updateBaselineStyle(style) {
53056
54237
  const baselineDescr = { ...this.baselineStyle, ...style };
53057
- this.props.updateChart(this.props.figureId, { baselineDescr });
54238
+ this.props.updateChart(this.props.chartId, { baselineDescr });
53058
54239
  }
53059
54240
  }
53060
54241
 
@@ -53072,7 +54253,7 @@ stores.inject(MyMetaStore, storeInstance);
53072
54253
  PieHoleSize,
53073
54254
  };
53074
54255
  static props = {
53075
- figureId: String,
54256
+ chartId: String,
53076
54257
  definition: Object,
53077
54258
  updateChart: Function,
53078
54259
  canUpdateChart: { type: Function, optional: true },
@@ -53085,18 +54266,18 @@ stores.inject(MyMetaStore, storeInstance);
53085
54266
  return this.props.definition.showLabels ?? SunburstChartDefaults.showLabels;
53086
54267
  }
53087
54268
  get groupColors() {
53088
- const figureId = this.props.figureId;
53089
- const runtime = this.env.model.getters.getChartRuntime(figureId);
54269
+ const chartId = this.props.chartId;
54270
+ const runtime = this.env.model.getters.getChartRuntime(chartId);
53090
54271
  const dataset = runtime.chartJsConfig.data.datasets[0];
53091
54272
  return dataset?.groupColors || [];
53092
54273
  }
53093
54274
  onGroupColorChanged(index, color) {
53094
54275
  const colors = deepCopy(this.props.definition.groupColors) ?? [];
53095
54276
  colors[index] = color;
53096
- this.props.updateChart(this.props.figureId, { groupColors: colors });
54277
+ this.props.updateChart(this.props.chartId, { groupColors: colors });
53097
54278
  }
53098
54279
  onPieHoleSizeChange(pieHolePercentage) {
53099
- this.props.updateChart(this.props.figureId, {
54280
+ this.props.updateChart(this.props.chartId, {
53100
54281
  ...this.props.definition,
53101
54282
  pieHolePercentage,
53102
54283
  });
@@ -53110,7 +54291,7 @@ stores.inject(MyMetaStore, storeInstance);
53110
54291
  RoundColorPicker,
53111
54292
  };
53112
54293
  static props = {
53113
- figureId: String,
54294
+ chartId: String,
53114
54295
  definition: Object,
53115
54296
  onColorChanged: Function,
53116
54297
  };
@@ -53122,7 +54303,7 @@ stores.inject(MyMetaStore, storeInstance);
53122
54303
  return coloringOptions;
53123
54304
  }
53124
54305
  getTreeGroupAndColors() {
53125
- const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
54306
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
53126
54307
  const config = runtime.chartJsConfig;
53127
54308
  return config.data.datasets[0]?.groupColors || [];
53128
54309
  }
@@ -53143,7 +54324,7 @@ stores.inject(MyMetaStore, storeInstance);
53143
54324
  static template = "o-spreadsheet-TreeMapColorScale";
53144
54325
  static components = { RoundColorPicker };
53145
54326
  static props = {
53146
- figureId: String,
54327
+ chartId: String,
53147
54328
  definition: Object,
53148
54329
  onColorChanged: Function,
53149
54330
  };
@@ -53185,7 +54366,7 @@ stores.inject(MyMetaStore, storeInstance);
53185
54366
  TreeMapColorScale,
53186
54367
  };
53187
54368
  static props = {
53188
- figureId: String,
54369
+ chartId: String,
53189
54370
  definition: Object,
53190
54371
  updateChart: Function,
53191
54372
  canUpdateChart: { type: Function, optional: true },
@@ -53209,15 +54390,15 @@ stores.inject(MyMetaStore, storeInstance);
53209
54390
  }
53210
54391
  changeColoringOption(option) {
53211
54392
  const coloringOptions = option === "categoryColor" ? this.savedColors.categoryColors : this.savedColors.colorScale;
53212
- this.props.updateChart(this.props.figureId, { coloringOptions });
54393
+ this.props.updateChart(this.props.chartId, { coloringOptions });
53213
54394
  }
53214
54395
  onCategoryColorChange(coloringOptions) {
53215
54396
  this.savedColors.categoryColors = coloringOptions;
53216
- this.props.updateChart(this.props.figureId, { coloringOptions });
54397
+ this.props.updateChart(this.props.chartId, { coloringOptions });
53217
54398
  }
53218
54399
  onColorScaleChange(coloringOptions) {
53219
54400
  this.savedColors.colorScale = coloringOptions;
53220
- this.props.updateChart(this.props.figureId, { coloringOptions });
54401
+ this.props.updateChart(this.props.chartId, { coloringOptions });
53221
54402
  }
53222
54403
  get coloringOptionChoices() {
53223
54404
  return [
@@ -53241,23 +54422,23 @@ stores.inject(MyMetaStore, storeInstance);
53241
54422
  ChartLegend,
53242
54423
  };
53243
54424
  static props = {
53244
- figureId: String,
54425
+ chartId: String,
53245
54426
  definition: Object,
53246
54427
  updateChart: Function,
53247
54428
  canUpdateChart: { type: Function, optional: true },
53248
54429
  };
53249
54430
  axisChoices = CHART_AXIS_CHOICES;
53250
54431
  onUpdateShowSubTotals(showSubTotals) {
53251
- this.props.updateChart(this.props.figureId, { showSubTotals });
54432
+ this.props.updateChart(this.props.chartId, { showSubTotals });
53252
54433
  }
53253
54434
  onUpdateShowConnectorLines(showConnectorLines) {
53254
- this.props.updateChart(this.props.figureId, { showConnectorLines });
54435
+ this.props.updateChart(this.props.chartId, { showConnectorLines });
53255
54436
  }
53256
54437
  onUpdateFirstValueAsSubtotal(firstValueAsSubtotal) {
53257
- this.props.updateChart(this.props.figureId, { firstValueAsSubtotal });
54438
+ this.props.updateChart(this.props.chartId, { firstValueAsSubtotal });
53258
54439
  }
53259
54440
  updateColor(colorName, color) {
53260
- this.props.updateChart(this.props.figureId, { [colorName]: color });
54441
+ this.props.updateChart(this.props.chartId, { [colorName]: color });
53261
54442
  }
53262
54443
  get axesList() {
53263
54444
  return [
@@ -53278,10 +54459,15 @@ stores.inject(MyMetaStore, storeInstance);
53278
54459
  CHART_WATERFALL_SUBTOTAL_COLOR);
53279
54460
  }
53280
54461
  updateVerticalAxisPosition(value) {
53281
- this.props.updateChart(this.props.figureId, {
54462
+ this.props.updateChart(this.props.chartId, {
53282
54463
  verticalAxisPosition: value,
53283
54464
  });
53284
54465
  }
54466
+ onToggleZoom(zoomable) {
54467
+ this.props.updateChart(this.props.chartId, {
54468
+ zoomable,
54469
+ });
54470
+ }
53285
54471
  }
53286
54472
 
53287
54473
  const chartSidePanelComponentRegistry = new Registry();
@@ -53292,11 +54478,11 @@ stores.inject(MyMetaStore, storeInstance);
53292
54478
  })
53293
54479
  .add("scatter", {
53294
54480
  configuration: ScatterConfigPanel,
53295
- design: ChartWithAxisDesignPanel,
54481
+ design: GenericZoomableChartDesignPanel,
53296
54482
  })
53297
54483
  .add("bar", {
53298
54484
  configuration: BarConfigPanel,
53299
- design: ChartWithAxisDesignPanel,
54485
+ design: GenericZoomableChartDesignPanel,
53300
54486
  })
53301
54487
  .add("combo", {
53302
54488
  configuration: GenericChartConfigPanel,
@@ -53380,7 +54566,7 @@ stores.inject(MyMetaStore, storeInstance);
53380
54566
  class ChartTypePicker extends owl.Component {
53381
54567
  static template = "o-spreadsheet-ChartTypePicker";
53382
54568
  static components = { Section, Popover };
53383
- static props = { figureId: String, chartPanelStore: Object };
54569
+ static props = { chartId: String, chartPanelStore: Object };
53384
54570
  categories = chartCategories;
53385
54571
  chartTypeByCategories = {};
53386
54572
  popoverRef = owl.useRef("popoverRef");
@@ -53405,14 +54591,14 @@ stores.inject(MyMetaStore, storeInstance);
53405
54591
  this.closePopover();
53406
54592
  }
53407
54593
  onTypeChange(type) {
53408
- this.props.chartPanelStore.changeChartType(this.props.figureId, type);
54594
+ this.props.chartPanelStore.changeChartType(this.props.chartId, type);
53409
54595
  this.closePopover();
53410
54596
  }
53411
- getChartDefinition(figureId) {
53412
- return this.env.model.getters.getChartDefinition(figureId);
54597
+ getChartDefinition(chartId) {
54598
+ return this.env.model.getters.getChartDefinition(chartId);
53413
54599
  }
53414
54600
  getSelectedChartSubtypeProperties() {
53415
- const definition = this.getChartDefinition(this.props.figureId);
54601
+ const definition = this.getChartDefinition(this.props.chartId);
53416
54602
  const matchedChart = chartSubtypeRegistry
53417
54603
  .getAll()
53418
54604
  .find((c) => c.matcher?.(definition) || false);
@@ -53444,34 +54630,36 @@ stores.inject(MyMetaStore, storeInstance);
53444
54630
  activatePanel(panel) {
53445
54631
  this.panel = panel;
53446
54632
  }
53447
- changeChartType(figureId, newDisplayType) {
53448
- const currentCreationContext = this.getters.getContextCreationChart(figureId);
53449
- const savedCreationContext = this.creationContexts[figureId] || {};
54633
+ changeChartType(chartId, newDisplayType) {
54634
+ const currentCreationContext = this.getters.getContextCreationChart(chartId);
54635
+ const savedCreationContext = this.creationContexts[chartId] || {};
53450
54636
  let newRanges = currentCreationContext?.range;
53451
54637
  if (newRanges?.every((range, i) => deepEquals(range, savedCreationContext.range?.[i]))) {
53452
54638
  newRanges = Object.assign([], savedCreationContext.range, currentCreationContext?.range);
53453
54639
  }
53454
- this.creationContexts[figureId] = {
54640
+ this.creationContexts[chartId] = {
53455
54641
  ...savedCreationContext,
53456
54642
  ...currentCreationContext,
53457
54643
  range: newRanges,
53458
54644
  };
54645
+ const figureId = this.getters.getFigureIdFromChartId(chartId);
53459
54646
  const sheetId = this.getters.getFigureSheetId(figureId);
53460
54647
  if (!sheetId) {
53461
54648
  return;
53462
54649
  }
53463
- const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
54650
+ const definition = this.getChartDefinitionFromContextCreation(chartId, newDisplayType);
53464
54651
  this.model.dispatch("UPDATE_CHART", {
53465
54652
  definition,
54653
+ chartId,
53466
54654
  figureId,
53467
54655
  sheetId,
53468
54656
  });
53469
54657
  }
53470
- getChartDefinitionFromContextCreation(figureId, newDisplayType) {
54658
+ getChartDefinitionFromContextCreation(chartId, newDisplayType) {
53471
54659
  const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
53472
54660
  const ChartClass = chartRegistry.get(newChartInfo.chartType);
53473
54661
  return {
53474
- ...ChartClass.getChartDefinitionFromContextCreation(this.creationContexts[figureId]),
54662
+ ...ChartClass.getChartDefinitionFromContextCreation(this.creationContexts[chartId]),
53475
54663
  ...newChartInfo.subtypeDefinition,
53476
54664
  };
53477
54665
  }
@@ -53512,15 +54700,15 @@ stores.inject(MyMetaStore, storeInstance);
53512
54700
  class ChartPanel extends owl.Component {
53513
54701
  static template = "o-spreadsheet-ChartPanel";
53514
54702
  static components = { Section, ChartTypePicker };
53515
- static props = { onCloseSidePanel: Function, figureId: String };
54703
+ static props = { onCloseSidePanel: Function, chartId: String };
53516
54704
  panelContentRef;
53517
54705
  scrollPositions = {
53518
54706
  configuration: 0,
53519
54707
  design: 0,
53520
54708
  };
53521
54709
  store;
53522
- get figureId() {
53523
- return this.props.figureId;
54710
+ get chartId() {
54711
+ return this.props.chartId;
53524
54712
  }
53525
54713
  setup() {
53526
54714
  this.store = useLocalStore(MainChartPanelStore);
@@ -53540,45 +54728,49 @@ stores.inject(MyMetaStore, storeInstance);
53540
54728
  }
53541
54729
  this.store.activatePanel(panel);
53542
54730
  }
53543
- updateChart(figureId, updateDefinition) {
53544
- if (figureId !== this.figureId) {
54731
+ updateChart(chartId, updateDefinition) {
54732
+ const figureId = this.env.model.getters.getFigureIdFromChartId(chartId);
54733
+ if (chartId !== this.chartId) {
53545
54734
  return;
53546
54735
  }
53547
54736
  const definition = {
53548
- ...this.getChartDefinition(this.figureId),
54737
+ ...this.getChartDefinition(this.chartId),
53549
54738
  ...updateDefinition,
53550
54739
  };
53551
54740
  return this.env.model.dispatch("UPDATE_CHART", {
53552
54741
  definition,
54742
+ chartId,
53553
54743
  figureId,
53554
54744
  sheetId: this.env.model.getters.getFigureSheetId(figureId),
53555
54745
  });
53556
54746
  }
53557
- canUpdateChart(figureId, updateDefinition) {
53558
- if (figureId !== this.figureId || !this.env.model.getters.isChartDefined(figureId)) {
54747
+ canUpdateChart(chartId, updateDefinition) {
54748
+ const figureId = this.env.model.getters.getFigureIdFromChartId(chartId);
54749
+ if (chartId !== this.chartId || !this.env.model.getters.isChartDefined(chartId)) {
53559
54750
  return;
53560
54751
  }
53561
54752
  const definition = {
53562
- ...this.getChartDefinition(this.figureId),
54753
+ ...this.getChartDefinition(this.chartId),
53563
54754
  ...updateDefinition,
53564
54755
  };
53565
54756
  return this.env.model.canDispatch("UPDATE_CHART", {
53566
54757
  definition,
54758
+ chartId,
53567
54759
  figureId,
53568
54760
  sheetId: this.env.model.getters.getFigureSheetId(figureId),
53569
54761
  });
53570
54762
  }
53571
54763
  onTypeChange(type) {
53572
- if (!this.figureId) {
54764
+ if (!this.chartId) {
53573
54765
  return;
53574
54766
  }
53575
- this.store.changeChartType(this.figureId, type);
54767
+ this.store.changeChartType(this.chartId, type);
53576
54768
  }
53577
54769
  get chartPanel() {
53578
- if (!this.figureId) {
54770
+ if (!this.chartId) {
53579
54771
  throw new Error("Chart not defined.");
53580
54772
  }
53581
- const type = this.env.model.getters.getChartType(this.figureId);
54773
+ const type = this.env.model.getters.getChartType(this.chartId);
53582
54774
  if (!type) {
53583
54775
  throw new Error("Chart not defined.");
53584
54776
  }
@@ -53588,8 +54780,8 @@ stores.inject(MyMetaStore, storeInstance);
53588
54780
  }
53589
54781
  return chartPanel;
53590
54782
  }
53591
- getChartDefinition(figureId) {
53592
- return this.env.model.getters.getChartDefinition(figureId);
54783
+ getChartDefinition(chartId) {
54784
+ return this.env.model.getters.getChartDefinition(chartId);
53593
54785
  }
53594
54786
  }
53595
54787
 
@@ -55526,11 +56718,15 @@ stores.inject(MyMetaStore, storeInstance);
55526
56718
  this.store = useLocalStore(PivotMeasureDisplayPanelStore, this.props.pivotId, this.props.measure);
55527
56719
  }
55528
56720
  onSave() {
55529
- this.env.openSidePanel("PivotSidePanel", { pivotId: this.props.pivotId });
56721
+ this.env.replaceSidePanel("PivotSidePanel", `pivot_measure_display_${this.props.pivotId}_${this.props.measure.id}`, {
56722
+ pivotId: this.props.pivotId,
56723
+ });
55530
56724
  }
55531
56725
  onCancel() {
55532
56726
  this.store.cancelMeasureDisplayEdition();
55533
- this.env.openSidePanel("PivotSidePanel", { pivotId: this.props.pivotId });
56727
+ this.env.replaceSidePanel("PivotSidePanel", `pivot_measure_display_${this.props.pivotId}_${this.props.measure.id}`, {
56728
+ pivotId: this.props.pivotId,
56729
+ });
55534
56730
  }
55535
56731
  get fieldChoices() {
55536
56732
  return this.store.fields.map((field) => ({
@@ -55591,103 +56787,6 @@ stores.inject(MyMetaStore, storeInstance);
55591
56787
  }
55592
56788
  }
55593
56789
 
55594
- function useAutofocus({ refName }) {
55595
- const ref = owl.useRef(refName);
55596
- owl.useEffect((el) => {
55597
- el?.focus();
55598
- }, () => [ref.el]);
55599
- }
55600
-
55601
- css /* scss */ `
55602
- .o-spreadsheet {
55603
- .os-input {
55604
- border-width: 0 0 1px 0;
55605
- border-color: transparent;
55606
- outline: none;
55607
- text-overflow: ellipsis;
55608
- color: ${TEXT_BODY};
55609
- }
55610
- .os-input:hover,
55611
- .os-input:focus {
55612
- border-color: ${GRAY_300};
55613
- }
55614
- }
55615
- `;
55616
- class TextInput extends owl.Component {
55617
- static template = "o-spreadsheet-TextInput";
55618
- static props = {
55619
- value: String,
55620
- onChange: Function,
55621
- class: {
55622
- type: String,
55623
- optional: true,
55624
- },
55625
- id: {
55626
- type: String,
55627
- optional: true,
55628
- },
55629
- placeholder: {
55630
- type: String,
55631
- optional: true,
55632
- },
55633
- autofocus: {
55634
- type: Boolean,
55635
- optional: true,
55636
- },
55637
- };
55638
- inputRef = owl.useRef("input");
55639
- setup() {
55640
- owl.useExternalListener(window, "click", (ev) => {
55641
- if (ev.target !== this.inputRef.el && this.inputRef.el?.value !== this.props.value) {
55642
- this.save();
55643
- }
55644
- }, { capture: true });
55645
- if (this.props.autofocus) {
55646
- useAutofocus({ refName: "input" });
55647
- }
55648
- }
55649
- onKeyDown(ev) {
55650
- switch (ev.key) {
55651
- case "Enter":
55652
- this.save();
55653
- ev.preventDefault();
55654
- ev.stopPropagation();
55655
- break;
55656
- case "Escape":
55657
- if (this.inputRef.el) {
55658
- this.inputRef.el.value = this.props.value;
55659
- this.inputRef.el.blur();
55660
- }
55661
- ev.preventDefault();
55662
- ev.stopPropagation();
55663
- break;
55664
- }
55665
- }
55666
- save() {
55667
- const currentValue = (this.inputRef.el?.value || "").trim();
55668
- if (currentValue !== this.props.value) {
55669
- this.props.onChange(currentValue);
55670
- }
55671
- this.inputRef.el?.blur();
55672
- }
55673
- onMouseDown(ev) {
55674
- // Stop the event if the input is not focused, we handle everything in onMouseUp
55675
- if (ev.target !== document.activeElement) {
55676
- ev.preventDefault();
55677
- ev.stopPropagation();
55678
- }
55679
- }
55680
- onMouseUp(ev) {
55681
- const target = ev.target;
55682
- if (target !== document.activeElement) {
55683
- target.focus();
55684
- target.select();
55685
- ev.preventDefault();
55686
- ev.stopPropagation();
55687
- }
55688
- }
55689
- }
55690
-
55691
56790
  class PivotCustomGroupsCollapsible extends owl.Component {
55692
56791
  static template = "o-spreadsheet-PivotCustomGroupsCollapsible";
55693
56792
  static props = {
@@ -56044,7 +57143,7 @@ stores.inject(MyMetaStore, storeInstance);
56044
57143
  });
56045
57144
  }
56046
57145
  openShowValuesAs() {
56047
- this.env.openSidePanel("PivotMeasureDisplayPanel", {
57146
+ this.env.replaceSidePanel("PivotMeasureDisplayPanel", `pivot_key_${this.props.pivotId}`, {
56048
57147
  pivotId: this.props.pivotId,
56049
57148
  measure: this.props.measure,
56050
57149
  });
@@ -56305,7 +57404,7 @@ stores.inject(MyMetaStore, storeInstance);
56305
57404
  this.props.onDimensionsUpdated(update);
56306
57405
  }
56307
57406
  getMeasureId(fieldName, aggregator) {
56308
- const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
57407
+ const baseId = fieldName.replaceAll("'", "") + (aggregator ? `:${aggregator}` : "");
56309
57408
  let id = baseId;
56310
57409
  let i = 2;
56311
57410
  while (this.props.definition.measures.some((m) => m.id === id)) {
@@ -56383,6 +57482,13 @@ stores.inject(MyMetaStore, storeInstance);
56383
57482
  const fieldName = field ? getFieldDisplayName(field) : "";
56384
57483
  return measureDisplayTerms.descriptions[measureDisplay.type](fieldName);
56385
57484
  }
57485
+ getHugeDimensionErrorMessage(dimension) {
57486
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
57487
+ const possibleValues = pivot.getPossibleFieldValues(dimension);
57488
+ return possibleValues.length > 100
57489
+ ? _t("This dimension contains a lot of values (%s), and might slow down the pivot table.", possibleValues.length)
57490
+ : undefined;
57491
+ }
56386
57492
  }
56387
57493
 
56388
57494
  class PivotTitleSection extends owl.Component {
@@ -56556,6 +57662,7 @@ stores.inject(MyMetaStore, storeInstance);
56556
57662
  draft = null;
56557
57663
  notification = this.get(NotificationStore);
56558
57664
  alreadyNotified = false;
57665
+ alreadyNotifiedForPivotSize = false;
56559
57666
  constructor(get, pivotId) {
56560
57667
  super(get);
56561
57668
  this.pivotId = pivotId;
@@ -56674,6 +57781,16 @@ stores.inject(MyMetaStore, storeInstance);
56674
57781
  sticky: true,
56675
57782
  });
56676
57783
  }
57784
+ const pivot = this.getters.getPivot(this.pivotId);
57785
+ const numberOfCells = pivot.isValid() ? pivot.getExpandedTableStructure().numberOfCells : 0;
57786
+ if (!this.alreadyNotifiedForPivotSize && numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
57787
+ this.alreadyNotifiedForPivotSize = true;
57788
+ this.notification.notifyUser({
57789
+ type: "warning",
57790
+ text: getPivotTooBigErrorMessage(numberOfCells, this.getters.getLocale()),
57791
+ sticky: true,
57792
+ });
57793
+ }
56677
57794
  }
56678
57795
  }
56679
57796
  discardPendingUpdate() {
@@ -57802,11 +58919,12 @@ stores.inject(MyMetaStore, storeInstance);
57802
58919
  title: _t("Chart"),
57803
58920
  Body: ChartPanel,
57804
58921
  computeState: (getters, initialProps) => {
57805
- const figureId = getters.getSelectedFigureId() ?? initialProps.figureId;
57806
- if (!getters.isChartDefined(figureId)) {
58922
+ const figureId = getters.getSelectedFigureId();
58923
+ const chartId = figureId ? getters.getChartIdFromFigureId(figureId) : initialProps.chartId;
58924
+ if (!chartId || !getters.isChartDefined(chartId)) {
57807
58925
  return { isOpen: false };
57808
58926
  }
57809
- return { isOpen: true, props: { figureId } };
58927
+ return { isOpen: true, props: { chartId } };
57810
58928
  },
57811
58929
  });
57812
58930
  sidePanelRegistry.add("FindAndReplace", {
@@ -57887,13 +59005,28 @@ stores.inject(MyMetaStore, storeInstance);
57887
59005
  try {
57888
59006
  // This will throw if the pivot or measure does not exist
57889
59007
  getters.getPivot(props.pivotId).getMeasure(props.measure.id);
57890
- return { isOpen: true, props, key: "pivot_measure_display" };
59008
+ return {
59009
+ isOpen: true,
59010
+ props,
59011
+ key: `pivot_measure_display_${props.pivotId}_${props.measure.id}`,
59012
+ };
57891
59013
  }
57892
59014
  catch (e) {
57893
59015
  return { isOpen: false };
57894
59016
  }
57895
59017
  },
57896
59018
  });
59019
+ sidePanelRegistry.add("CarouselPanel", {
59020
+ title: _t("Carousel"),
59021
+ Body: CarouselPanel,
59022
+ computeState: (getters, initialProps) => {
59023
+ const figureId = initialProps.figureId || getters.getSelectedFigureId();
59024
+ if (!figureId || !getters.doesCarouselExist(figureId)) {
59025
+ return { isOpen: false };
59026
+ }
59027
+ return { isOpen: true, props: { figureId } };
59028
+ },
59029
+ });
57897
59030
 
57898
59031
  class ScreenWidthStore {
57899
59032
  mutators = ["setSmallThreshhold"];
@@ -57912,6 +59045,7 @@ stores.inject(MyMetaStore, storeInstance);
57912
59045
  class SidePanelStore extends SpreadsheetStore {
57913
59046
  mutators = [
57914
59047
  "open",
59048
+ "replace",
57915
59049
  "toggle",
57916
59050
  "close",
57917
59051
  "changePanelSize",
@@ -57973,8 +59107,7 @@ stores.inject(MyMetaStore, storeInstance);
57973
59107
  if (!state.isOpen) {
57974
59108
  return;
57975
59109
  }
57976
- const mainPanelKey = this.mainPanel ? this.getPanelKey(this.mainPanel) : undefined;
57977
- if (!this.mainPanel || !this.mainPanel.isPinned || mainPanelKey === state.key) {
59110
+ if (!this.mainPanel || !this.mainPanel.isPinned || this.mainPanelKey === state.key) {
57978
59111
  this._openPanel("mainPanel", newPanelInfo, state);
57979
59112
  return;
57980
59113
  }
@@ -57993,6 +59126,34 @@ stores.inject(MyMetaStore, storeInstance);
57993
59126
  }
57994
59127
  this._openPanel("secondaryPanel", newPanelInfo, state);
57995
59128
  }
59129
+ replace(componentTag, currentPanelKey, initialPanelProps = {}) {
59130
+ const newPanelInfo = { initialPanelProps, componentTag, size: DEFAULT_SIDE_PANEL_SIZE };
59131
+ const state = this.computeState(newPanelInfo);
59132
+ if (!state.isOpen) {
59133
+ return;
59134
+ }
59135
+ const ensureMainPanelExpanded = () => {
59136
+ if (this.mainPanel?.isCollapsed) {
59137
+ this.toggleCollapsePanel("mainPanel");
59138
+ }
59139
+ };
59140
+ // Close the current panel if the target panel is already open
59141
+ const isMainPanel = this.mainPanelKey === state.key;
59142
+ const isSecondaryPanel = this.secondaryPanelKey === state.key;
59143
+ if (isMainPanel && this.secondaryPanel) {
59144
+ this.close();
59145
+ ensureMainPanelExpanded();
59146
+ return;
59147
+ }
59148
+ if (isSecondaryPanel) {
59149
+ this.closeMainPanel();
59150
+ this.togglePinPanel();
59151
+ ensureMainPanelExpanded();
59152
+ return;
59153
+ }
59154
+ const targetPanel = this.mainPanelKey === currentPanelKey ? "mainPanel" : "secondaryPanel";
59155
+ this._openPanel(targetPanel, newPanelInfo, state);
59156
+ }
57996
59157
  _openPanel(panel, newPanel, state) {
57997
59158
  const currentPanel = this[panel];
57998
59159
  if (currentPanel && newPanel.componentTag !== currentPanel.componentTag) {
@@ -58052,11 +59213,9 @@ stores.inject(MyMetaStore, storeInstance);
58052
59213
  panelInfo.size = size;
58053
59214
  }
58054
59215
  resetPanelSize(panel) {
58055
- const panelInfo = this[panel];
58056
- if (!panelInfo) {
58057
- return;
59216
+ if (this[panel]) {
59217
+ this[panel].size = DEFAULT_SIDE_PANEL_SIZE;
58058
59218
  }
58059
- panelInfo.size = DEFAULT_SIDE_PANEL_SIZE;
58060
59219
  }
58061
59220
  togglePinPanel() {
58062
59221
  if (!this.mainPanel) {
@@ -58855,7 +60014,7 @@ stores.inject(MyMetaStore, storeInstance);
58855
60014
  let lastFigureId = undefined;
58856
60015
  owl.onWillUpdateProps(() => {
58857
60016
  if (lastFigureId !== this.figureUI?.id) {
58858
- animationStore.enableAnimationForChart(this.figureUI?.id + "-fullscreen");
60017
+ animationStore.enableAnimationForChart(this.chartId + "-fullscreen");
58859
60018
  }
58860
60019
  lastFigureId = this.figureUI?.id;
58861
60020
  });
@@ -58864,6 +60023,11 @@ stores.inject(MyMetaStore, storeInstance);
58864
60023
  get figureUI() {
58865
60024
  return this.fullScreenChartStore.fullScreenFigure;
58866
60025
  }
60026
+ get chartId() {
60027
+ if (!this.figureUI)
60028
+ return undefined;
60029
+ return this.env.model.getters.getChartIdFromFigureId(this.figureUI?.id);
60030
+ }
58867
60031
  exitFullScreen() {
58868
60032
  if (this.figureUI) {
58869
60033
  this.fullScreenChartStore.toggleFullScreenChart(this.figureUI.id);
@@ -58875,9 +60039,9 @@ stores.inject(MyMetaStore, storeInstance);
58875
60039
  }
58876
60040
  }
58877
60041
  get chartComponent() {
58878
- if (!this.figureUI)
60042
+ if (!this.chartId)
58879
60043
  return undefined;
58880
- const type = this.env.model.getters.getChartType(this.figureUI.id);
60044
+ const type = this.env.model.getters.getChartType(this.chartId);
58881
60045
  const component = chartComponentRegistry.get(type);
58882
60046
  if (!component) {
58883
60047
  throw new Error(`Component is not defined for type ${type}`);
@@ -60536,6 +61700,7 @@ stores.inject(MyMetaStore, storeInstance);
60536
61700
  "getChartType",
60537
61701
  "getChartIds",
60538
61702
  "getChart",
61703
+ "getFigureIdFromChartId",
60539
61704
  "getContextCreationChart",
60540
61705
  ];
60541
61706
  charts = {};
@@ -60543,7 +61708,11 @@ stores.inject(MyMetaStore, storeInstance);
60543
61708
  validateChartDefinition = (cmd) => validateChartDefinition(this, cmd.definition);
60544
61709
  adaptRanges(applyChange) {
60545
61710
  for (const [chartId, chart] of Object.entries(this.charts)) {
60546
- this.history.update("charts", chartId, chart?.updateRanges(applyChange));
61711
+ if (!chart) {
61712
+ continue;
61713
+ }
61714
+ const newChart = chart.chart.updateRanges(applyChange);
61715
+ this.history.update("charts", chartId, newChart ? { figureId: chart.figureId, chart: newChart } : undefined);
60547
61716
  }
60548
61717
  }
60549
61718
  // ---------------------------------------------------------------------------
@@ -60552,9 +61721,11 @@ stores.inject(MyMetaStore, storeInstance);
60552
61721
  allowDispatch(cmd) {
60553
61722
  switch (cmd.type) {
60554
61723
  case "CREATE_CHART":
60555
- return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
61724
+ return this.checkValidations(cmd, this.chainValidations(this.checkFigureArguments, this.validateChartDefinition, this.checkChartDuplicate));
60556
61725
  case "UPDATE_CHART":
60557
61726
  return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists, this.checkChartChanged));
61727
+ case "DELETE_CHART":
61728
+ return this.checkChartExists(cmd);
60558
61729
  default:
60559
61730
  return "Success" /* CommandResult.Success */;
60560
61731
  }
@@ -60562,37 +61733,61 @@ stores.inject(MyMetaStore, storeInstance);
60562
61733
  handle(cmd) {
60563
61734
  switch (cmd.type) {
60564
61735
  case "CREATE_CHART":
60565
- this.addFigure(cmd.figureId, cmd.sheetId, cmd.col, cmd.row, cmd.offset, cmd.size);
60566
- this.addChart(cmd.figureId, cmd.definition);
61736
+ const { col, row, offset, size, sheetId, figureId } = cmd;
61737
+ // If figure position is not defined, it means that the figure already exist (see allowDispatch)
61738
+ if (!this.getters.getFigure(sheetId, figureId) &&
61739
+ offset !== undefined &&
61740
+ col !== undefined &&
61741
+ row !== undefined) {
61742
+ this.addFigure(figureId, sheetId, col, row, offset, size);
61743
+ }
61744
+ this.addChart(cmd.figureId, cmd.chartId, cmd.definition);
60567
61745
  break;
60568
61746
  case "UPDATE_CHART": {
60569
- this.addChart(cmd.figureId, cmd.definition);
61747
+ this.addChart(cmd.figureId, cmd.chartId, cmd.definition);
60570
61748
  break;
60571
61749
  }
60572
61750
  case "DUPLICATE_SHEET": {
60573
- const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);
60574
- for (const fig of sheetFiguresFrom) {
60575
- if (fig.tag === "chart") {
60576
- const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
60577
- const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
60578
- const chart = this.charts[fig.id]?.duplicateInDuplicatedSheet(cmd.sheetIdTo);
60579
- if (chart) {
60580
- this.dispatch("CREATE_CHART", {
60581
- figureId: duplicatedFigureId,
60582
- col: fig.col,
60583
- row: fig.row,
60584
- offset: fig.offset,
60585
- size: { width: fig.width, height: fig.height },
60586
- definition: chart.getDefinition(),
60587
- sheetId: cmd.sheetIdTo,
60588
- });
60589
- }
61751
+ for (const chartId of this.getChartIds(cmd.sheetId)) {
61752
+ const { chart, figureId } = this.charts[chartId] || {};
61753
+ if (!chart || !figureId) {
61754
+ continue;
61755
+ }
61756
+ const fig = this.getters.getFigure(cmd.sheetId, figureId);
61757
+ if (!fig) {
61758
+ continue;
61759
+ }
61760
+ const figureIdBase = figureId.split(FIGURE_ID_SPLITTER).pop();
61761
+ const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
61762
+ const chartIdBase = chartId.split(FIGURE_ID_SPLITTER).pop();
61763
+ const duplicatedChartId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${chartIdBase}`;
61764
+ const newChart = chart.duplicateInDuplicatedSheet(cmd.sheetIdTo);
61765
+ if (newChart) {
61766
+ this.dispatch("CREATE_CHART", {
61767
+ figureId: duplicatedFigureId,
61768
+ chartId: duplicatedChartId,
61769
+ col: fig.col,
61770
+ row: fig.row,
61771
+ offset: fig.offset,
61772
+ size: { width: fig.width, height: fig.height },
61773
+ definition: newChart.getDefinition(),
61774
+ sheetId: cmd.sheetIdTo,
61775
+ });
60590
61776
  }
60591
61777
  }
60592
61778
  break;
60593
61779
  }
60594
61780
  case "DELETE_FIGURE":
60595
- this.history.update("charts", cmd.figureId, undefined);
61781
+ for (const chartId in this.charts) {
61782
+ if (this.charts[chartId]?.figureId === cmd.figureId) {
61783
+ this.dispatch("DELETE_CHART", { chartId, sheetId: cmd.sheetId });
61784
+ }
61785
+ }
61786
+ break;
61787
+ case "DELETE_CHART":
61788
+ if (this.isChartDefined(cmd.chartId)) {
61789
+ this.history.update("charts", cmd.chartId, undefined);
61790
+ }
60596
61791
  break;
60597
61792
  case "DELETE_SHEET":
60598
61793
  for (const id of this.getChartIds(cmd.sheetId)) {
@@ -60604,31 +61799,37 @@ stores.inject(MyMetaStore, storeInstance);
60604
61799
  // ---------------------------------------------------------------------------
60605
61800
  // Getters
60606
61801
  // ---------------------------------------------------------------------------
60607
- getContextCreationChart(figureId) {
60608
- return this.charts[figureId]?.getContextCreation();
61802
+ getContextCreationChart(chartId) {
61803
+ return this.charts[chartId]?.chart.getContextCreation();
60609
61804
  }
60610
- getChart(figureId) {
60611
- return this.charts[figureId];
61805
+ getChart(chartId) {
61806
+ return this.charts[chartId]?.chart;
61807
+ }
61808
+ getFigureIdFromChartId(chartId) {
61809
+ if (!this.charts[chartId]) {
61810
+ throw new Error(`Chart with id ${chartId} does not exist.`);
61811
+ }
61812
+ return this.charts[chartId].figureId;
60612
61813
  }
60613
- getChartType(figureId) {
60614
- const type = this.charts[figureId]?.type;
61814
+ getChartType(chartId) {
61815
+ const type = this.charts[chartId]?.chart.type;
60615
61816
  if (!type) {
60616
61817
  throw new Error("Chart not defined.");
60617
61818
  }
60618
61819
  return type;
60619
61820
  }
60620
- isChartDefined(figureId) {
60621
- return figureId in this.charts && this.charts !== undefined;
61821
+ isChartDefined(chartId) {
61822
+ return chartId in this.charts && this.charts !== undefined;
60622
61823
  }
60623
61824
  getChartIds(sheetId) {
60624
61825
  return Object.entries(this.charts)
60625
- .filter(([, chart]) => chart?.sheetId === sheetId)
61826
+ .filter(([, chart]) => chart?.chart.sheetId === sheetId)
60626
61827
  .map(([id]) => id);
60627
61828
  }
60628
- getChartDefinition(figureId) {
60629
- const definition = this.charts[figureId]?.getDefinition();
61829
+ getChartDefinition(chartId) {
61830
+ const definition = this.charts[chartId]?.chart.getDefinition();
60630
61831
  if (!definition) {
60631
- throw new Error(`There is no chart with the given figureId: ${figureId}`);
61832
+ throw new Error(`There is no chart with the given id: ${chartId}`);
60632
61833
  }
60633
61834
  return definition;
60634
61835
  }
@@ -60643,7 +61844,16 @@ stores.inject(MyMetaStore, storeInstance);
60643
61844
  // figure data should be external IMO => chart should be in sheet.chart
60644
61845
  // instead of in figure.data
60645
61846
  if (figure.tag === "chart") {
60646
- this.charts[figure.id] = this.createChart(figure.id, figure.data, sheet.id);
61847
+ const chartId = figure.data.chartId;
61848
+ const chart = this.createChart(figure.id, figure.data, sheet.id);
61849
+ this.charts[chartId] = { chart, figureId: figure.id };
61850
+ }
61851
+ else if (figure.tag === "carousel") {
61852
+ for (const chartId in figure.data.chartDefinitions || {}) {
61853
+ const chartDefinition = figure.data.chartDefinitions[chartId];
61854
+ const chart = this.createChart(figure.id, chartDefinition, sheet.id);
61855
+ this.charts[chartId] = { chart, figureId: figure.id };
61856
+ }
60647
61857
  }
60648
61858
  }
60649
61859
  }
@@ -60657,13 +61867,23 @@ stores.inject(MyMetaStore, storeInstance);
60657
61867
  const figures = [];
60658
61868
  for (const sheetFigure of sheetFigures) {
60659
61869
  const figure = sheetFigure;
60660
- if (figure && figure.tag === "chart") {
60661
- const data = this.charts[figure.id]?.getDefinition();
61870
+ const chartId = Object.keys(this.charts).find((chartId) => this.charts[chartId]?.figureId === sheetFigure.id);
61871
+ if (figure && figure.tag === "chart" && chartId) {
61872
+ const data = this.charts[chartId]?.chart.getDefinition();
60662
61873
  if (data) {
60663
- figure.data = data;
61874
+ figure.data = { ...data, chartId };
60664
61875
  figures.push(figure);
60665
61876
  }
60666
61877
  }
61878
+ else if (figure && figure.tag === "carousel") {
61879
+ const chartIds = Object.keys(this.charts).filter((chartId) => this.charts[chartId]?.figureId === sheetFigure.id);
61880
+ const data = {};
61881
+ for (const chartId of chartIds) {
61882
+ data[chartId] = this.charts[chartId]?.chart.getDefinition();
61883
+ }
61884
+ figure.data = { chartDefinitions: data };
61885
+ figures.push(figure);
61886
+ }
60667
61887
  else {
60668
61888
  figures.push(figure);
60669
61889
  }
@@ -60696,27 +61916,40 @@ stores.inject(MyMetaStore, storeInstance);
60696
61916
  * Add a chart in the local state. If a chart already exists, this chart is
60697
61917
  * replaced
60698
61918
  */
60699
- addChart(id, definition) {
60700
- const sheetId = this.getters.getFigureSheetId(id);
61919
+ addChart(figureId, chartId, definition) {
61920
+ const sheetId = this.getters.getFigureSheetId(figureId);
60701
61921
  if (sheetId) {
60702
- this.history.update("charts", id, this.createChart(id, definition, sheetId));
61922
+ const chart = this.createChart(figureId, definition, sheetId);
61923
+ this.history.update("charts", chartId, { figureId, chart });
60703
61924
  }
60704
61925
  }
60705
61926
  checkChartDuplicate(cmd) {
60706
- return this.getters.getFigureSheetId(cmd.figureId)
61927
+ return this.isChartDefined(cmd.chartId)
60707
61928
  ? "DuplicatedChartId" /* CommandResult.DuplicatedChartId */
60708
61929
  : "Success" /* CommandResult.Success */;
60709
61930
  }
60710
61931
  checkChartExists(cmd) {
60711
- return this.isChartDefined(cmd.figureId)
61932
+ return this.isChartDefined(cmd.chartId)
60712
61933
  ? "Success" /* CommandResult.Success */
60713
61934
  : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
60714
61935
  }
60715
61936
  checkChartChanged(cmd) {
60716
- return deepEquals(this.getChartDefinition(cmd.figureId), cmd.definition)
61937
+ if (cmd.figureId !== this.charts[cmd.chartId]?.figureId) {
61938
+ return "Success" /* CommandResult.Success */;
61939
+ }
61940
+ return deepEquals(this.getChartDefinition(cmd.chartId), cmd.definition)
60717
61941
  ? "NoChanges" /* CommandResult.NoChanges */
60718
61942
  : "Success" /* CommandResult.Success */;
60719
61943
  }
61944
+ /** If the command would create a new figure, there need to be a position & offset defined in the command */
61945
+ checkFigureArguments(cmd) {
61946
+ if (this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
61947
+ return "Success" /* CommandResult.Success */;
61948
+ }
61949
+ return cmd.offset !== undefined && cmd.col !== undefined && cmd.row !== undefined
61950
+ ? "Success" /* CommandResult.Success */
61951
+ : "MissingFigureArguments" /* CommandResult.MissingFigureArguments */;
61952
+ }
60720
61953
  }
60721
61954
 
60722
61955
  // -----------------------------------------------------------------------------
@@ -61570,6 +62803,23 @@ stores.inject(MyMetaStore, storeInstance);
61570
62803
  this.onRowRemove(cmd.sheetId);
61571
62804
  }
61572
62805
  break;
62806
+ case "DUPLICATE_SHEET": {
62807
+ for (const figureId in this.figures[cmd.sheetId]) {
62808
+ const fig = this.figures[cmd.sheetId]?.[figureId];
62809
+ if (!fig) {
62810
+ continue;
62811
+ }
62812
+ const figureIdBase = figureId.split(FIGURE_ID_SPLITTER).pop();
62813
+ const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
62814
+ this.dispatch("CREATE_FIGURE", {
62815
+ figureId: duplicatedFigureId,
62816
+ ...fig,
62817
+ size: { width: fig.width, height: fig.height },
62818
+ sheetId: cmd.sheetIdTo,
62819
+ });
62820
+ }
62821
+ break;
62822
+ }
61573
62823
  }
61574
62824
  }
61575
62825
  onColRemove(sheetId) {
@@ -61681,6 +62931,9 @@ stores.inject(MyMetaStore, storeInstance);
61681
62931
  const figure = { ...this.getFigure(sheetId, figureId), ...update };
61682
62932
  for (const [key, value] of Object.entries(update)) {
61683
62933
  switch (key) {
62934
+ case "tag":
62935
+ this.history.update("figures", sheetId, figure.id, key, value);
62936
+ break;
61684
62937
  case "offset":
61685
62938
  this.history.update("figures", sheetId, figure.id, key, value);
61686
62939
  break;
@@ -62108,7 +63361,9 @@ stores.inject(MyMetaStore, storeInstance);
62108
63361
  handle(cmd) {
62109
63362
  switch (cmd.type) {
62110
63363
  case "CREATE_IMAGE":
62111
- this.addFigure(cmd.figureId, cmd.sheetId, cmd.col, cmd.row, cmd.offset, cmd.size);
63364
+ if (!this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
63365
+ this.addFigure(cmd.figureId, cmd.sheetId, cmd.col, cmd.row, cmd.offset, cmd.size);
63366
+ }
62112
63367
  this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
62113
63368
  this.syncedImages.add(cmd.definition.path);
62114
63369
  break;
@@ -64246,6 +65501,122 @@ stores.inject(MyMetaStore, storeInstance);
64246
65501
  }
64247
65502
  }
64248
65503
 
65504
+ class CarouselPlugin extends CorePlugin {
65505
+ static getters = ["getCarousel", "doesCarouselExist"];
65506
+ carousels = {};
65507
+ allowDispatch(cmd) {
65508
+ switch (cmd.type) {
65509
+ case "CREATE_CAROUSEL": {
65510
+ if (this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
65511
+ return "DuplicatedFigureId" /* CommandResult.DuplicatedFigureId */;
65512
+ }
65513
+ return "Success" /* CommandResult.Success */;
65514
+ }
65515
+ case "UPDATE_CAROUSEL": {
65516
+ if (!this.carousels[cmd.sheetId]?.[cmd.figureId]) {
65517
+ return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
65518
+ }
65519
+ return "Success" /* CommandResult.Success */;
65520
+ }
65521
+ }
65522
+ return "Success" /* CommandResult.Success */;
65523
+ }
65524
+ handle(cmd) {
65525
+ switch (cmd.type) {
65526
+ case "CREATE_CAROUSEL":
65527
+ if (!this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
65528
+ this.dispatch("CREATE_FIGURE", { ...cmd, tag: "carousel" });
65529
+ }
65530
+ this.history.update("carousels", cmd.sheetId, cmd.figureId, cmd.definition);
65531
+ break;
65532
+ case "UPDATE_CAROUSEL":
65533
+ this.removeDeletedCharts(cmd, this.getters.getCarousel(cmd.figureId).items);
65534
+ this.history.update("carousels", cmd.sheetId, cmd.figureId, cmd.definition);
65535
+ break;
65536
+ case "DUPLICATE_SHEET": {
65537
+ const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);
65538
+ for (const fig of sheetFiguresFrom) {
65539
+ if (fig.tag === "carousel") {
65540
+ const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
65541
+ const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
65542
+ const carousel = this.getCarousel(fig.id);
65543
+ if (carousel) {
65544
+ const size = { width: fig.width, height: fig.height };
65545
+ this.dispatch("CREATE_CAROUSEL", {
65546
+ sheetId: cmd.sheetIdTo,
65547
+ figureId: duplicatedFigureId,
65548
+ offset: fig.offset,
65549
+ col: fig.col,
65550
+ row: fig.row,
65551
+ size,
65552
+ definition: {
65553
+ items: carousel.items.map((item) => {
65554
+ if (item.type === "carouselDataView") {
65555
+ return { type: "carouselDataView" };
65556
+ }
65557
+ const chartIdBase = item.chartId.split(FIGURE_ID_SPLITTER).pop();
65558
+ const newChartId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${chartIdBase}`;
65559
+ return { type: "chart", chartId: newChartId };
65560
+ }),
65561
+ },
65562
+ });
65563
+ }
65564
+ }
65565
+ }
65566
+ break;
65567
+ }
65568
+ case "DELETE_FIGURE":
65569
+ this.history.update("carousels", cmd.sheetId, cmd.figureId, undefined);
65570
+ break;
65571
+ case "DELETE_SHEET":
65572
+ this.history.update("carousels", cmd.sheetId, undefined);
65573
+ break;
65574
+ }
65575
+ }
65576
+ doesCarouselExist(figureId) {
65577
+ for (const sheetId in this.carousels) {
65578
+ if (this.carousels[sheetId]?.[figureId]) {
65579
+ return true;
65580
+ }
65581
+ }
65582
+ throw false;
65583
+ }
65584
+ getCarousel(figureId) {
65585
+ for (const sheetId in this.carousels) {
65586
+ if (this.carousels[sheetId]?.[figureId]) {
65587
+ return this.carousels[sheetId][figureId];
65588
+ }
65589
+ }
65590
+ throw new Error(`There is no carousel with the given figureId: ${figureId}`);
65591
+ }
65592
+ removeDeletedCharts(cmd, oldItems) {
65593
+ const newChartIds = new Set(cmd.definition.items.filter((item) => item.type === "chart").map((item) => item.chartId));
65594
+ for (const item of oldItems) {
65595
+ if (item.type === "chart" && !newChartIds.has(item.chartId)) {
65596
+ this.dispatch("DELETE_CHART", { chartId: item.chartId, sheetId: cmd.sheetId });
65597
+ }
65598
+ }
65599
+ }
65600
+ import(data) {
65601
+ for (const sheet of data.sheets) {
65602
+ const carousels = (sheet.figures || []).filter((figure) => figure.tag === "carousel");
65603
+ for (const carousel of carousels) {
65604
+ this.history.update("carousels", sheet.id, carousel.id, { items: carousel.data.items });
65605
+ }
65606
+ }
65607
+ }
65608
+ export(data) {
65609
+ for (const sheet of data.sheets) {
65610
+ const carousels = sheet.figures.filter((figure) => figure.tag === "carousel");
65611
+ for (const carousel of carousels) {
65612
+ if (this.carousels[sheet.id]?.[carousel.id]) {
65613
+ carousel.data = { ...carousel.data, ...this.carousels[sheet.id]?.[carousel.id] };
65614
+ }
65615
+ }
65616
+ }
65617
+ }
65618
+ }
65619
+
64249
65620
  class HeaderGroupingPlugin extends CorePlugin {
64250
65621
  static getters = [
64251
65622
  "getHeaderGroups",
@@ -67201,7 +68572,7 @@ stores.inject(MyMetaStore, storeInstance);
67201
68572
  break;
67202
68573
  case "UPDATE_CHART":
67203
68574
  case "CREATE_CHART":
67204
- this.tryToAddColors(this.getChartColors(cmd.figureId));
68575
+ this.tryToAddColors(this.getChartColors(cmd.chartId));
67205
68576
  break;
67206
68577
  case "UPDATE_CELL":
67207
68578
  case "ADD_CONDITIONAL_FORMAT":
@@ -67333,8 +68704,10 @@ stores.inject(MyMetaStore, storeInstance);
67333
68704
  switch (cmd.type) {
67334
68705
  case "UPDATE_CHART":
67335
68706
  case "CREATE_CHART":
67336
- case "DELETE_FIGURE":
67337
- this.charts[cmd.figureId] = undefined;
68707
+ this.charts[cmd.chartId] = undefined;
68708
+ break;
68709
+ case "DELETE_CHART":
68710
+ this.charts[cmd.chartId] = undefined;
67338
68711
  break;
67339
68712
  case "DELETE_SHEET":
67340
68713
  for (const chartId in this.charts) {
@@ -67345,15 +68718,15 @@ stores.inject(MyMetaStore, storeInstance);
67345
68718
  break;
67346
68719
  }
67347
68720
  }
67348
- getChartRuntime(figureId) {
67349
- if (!this.charts[figureId]) {
67350
- const chart = this.getters.getChart(figureId);
68721
+ getChartRuntime(chartId) {
68722
+ if (!this.charts[chartId]) {
68723
+ const chart = this.getters.getChart(chartId);
67351
68724
  if (!chart) {
67352
- throw new Error(`No chart for the given id: ${figureId}`);
68725
+ throw new Error(`No chart for the given id: ${chartId}`);
67353
68726
  }
67354
- this.charts[figureId] = this.createRuntimeChart(chart);
68727
+ this.charts[chartId] = this.createRuntimeChart(chart);
67355
68728
  }
67356
- return this.charts[figureId];
68729
+ return this.charts[chartId];
67357
68730
  }
67358
68731
  /**
67359
68732
  * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
@@ -67388,8 +68761,14 @@ stores.inject(MyMetaStore, storeInstance);
67388
68761
  if (!figure || figure.tag !== "chart") {
67389
68762
  continue;
67390
68763
  }
67391
- const figureId = figure.id;
67392
- const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel(this.getters);
68764
+ const chartId = this.getters
68765
+ .getChartIds(sheet.id)
68766
+ .find((chartId) => this.getters.getFigureIdFromChartId(chartId) === figure.id);
68767
+ if (!chartId) {
68768
+ continue;
68769
+ }
68770
+ const chart = this.getters.getChart(chartId);
68771
+ const figureData = chart?.getDefinitionForExcel(this.getters);
67393
68772
  if (figureData) {
67394
68773
  figures.push({
67395
68774
  ...figure,
@@ -67397,12 +68776,11 @@ stores.inject(MyMetaStore, storeInstance);
67397
68776
  });
67398
68777
  }
67399
68778
  else {
67400
- const chart = this.getters.getChart(figureId);
67401
68779
  if (!chart) {
67402
68780
  continue;
67403
68781
  }
67404
- const type = this.getters.getChartType(figureId);
67405
- const runtime = this.getters.getChartRuntime(figureId);
68782
+ const type = this.getters.getChartType(chartId);
68783
+ const runtime = this.getters.getChartRuntime(chartId);
67406
68784
  const img = chartToImageUrl(runtime, figure, type);
67407
68785
  if (img) {
67408
68786
  sheet.images.push({
@@ -70536,7 +71914,9 @@ stores.inject(MyMetaStore, storeInstance);
70536
71914
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["ADD_COLUMNS_ROWS"], addHeadersTransformation);
70537
71915
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["ADD_COLUMNS_ROWS"], addHeadersTransformation);
70538
71916
  otRegistry.addTransformation("DELETE_SHEET", ["MOVE_RANGES"], transformTargetSheetId);
70539
- otRegistry.addTransformation("DELETE_FIGURE", ["UPDATE_FIGURE", "UPDATE_CHART"], updateChartFigure);
71917
+ otRegistry.addTransformation("DELETE_FIGURE", ["UPDATE_FIGURE", "UPDATE_CHART", "UPDATE_CAROUSEL"], updateChartFigure);
71918
+ otRegistry.addTransformation("DELETE_CHART", ["UPDATE_CHART"], updateChartOnChartDelete);
71919
+ otRegistry.addTransformation("DELETE_CHART", ["UPDATE_CAROUSEL"], updateCarouselOnChartDelete);
70540
71920
  otRegistry.addTransformation("CREATE_SHEET", ["CREATE_SHEET"], createSheetTransformation);
70541
71921
  otRegistry.addTransformation("ADD_MERGE", ["ADD_MERGE", "REMOVE_MERGE"], mergeTransformation);
70542
71922
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["FREEZE_COLUMNS", "FREEZE_ROWS"], freezeTransformation);
@@ -70589,6 +71969,21 @@ stores.inject(MyMetaStore, storeInstance);
70589
71969
  }
70590
71970
  return toTransform;
70591
71971
  }
71972
+ function updateChartOnChartDelete(toTransform, executed) {
71973
+ if (toTransform.chartId === executed.chartId) {
71974
+ return undefined;
71975
+ }
71976
+ return toTransform;
71977
+ }
71978
+ function updateCarouselOnChartDelete(toTransform, executed) {
71979
+ return {
71980
+ ...toTransform,
71981
+ definition: {
71982
+ ...toTransform.definition,
71983
+ items: toTransform.definition.items.filter((item) => !(item.type === "chart" && item.chartId === executed.chartId)),
71984
+ },
71985
+ };
71986
+ }
70592
71987
  function createSheetTransformation(toTransform, executed) {
70593
71988
  if (toTransform.sheetId === executed.sheetId) {
70594
71989
  toTransform = { ...toTransform, sheetId: `${toTransform.sheetId}~` };
@@ -72983,9 +74378,11 @@ stores.inject(MyMetaStore, storeInstance);
72983
74378
 
72984
74379
  const uuidGenerator = new UuidGenerator();
72985
74380
  function repeatCreateChartCommand(getters, cmd) {
74381
+ const id = uuidGenerator.smallUuid();
72986
74382
  return {
72987
74383
  ...repeatSheetDependantCommand(getters, cmd),
72988
- figureId: uuidGenerator.smallUuid(),
74384
+ figureId: id,
74385
+ chartId: id,
72989
74386
  };
72990
74387
  }
72991
74388
  function repeatCreateImageCommand(getters, cmd) {
@@ -76195,6 +77592,123 @@ stores.inject(MyMetaStore, storeInstance);
76195
77592
  }
76196
77593
  }
76197
77594
 
77595
+ class CarouselUIPlugin extends UIPlugin {
77596
+ static getters = [
77597
+ "getSelectedCarouselItem",
77598
+ "getChartFromFigureId",
77599
+ "getChartIdFromFigureId",
77600
+ ];
77601
+ uuidGenerator = new UuidGenerator();
77602
+ carouselStates = {};
77603
+ handle(cmd) {
77604
+ switch (cmd.type) {
77605
+ case "ADD_NEW_CHART_TO_CAROUSEL":
77606
+ this.addNewChartToCarousel(cmd.figureId, cmd.sheetId);
77607
+ break;
77608
+ case "ADD_FIGURE_CHART_TO_CAROUSEL":
77609
+ this.addFigureChartToCarousel(cmd.carouselFigureId, cmd.chartFigureId, cmd.sheetId);
77610
+ break;
77611
+ case "UPDATE_CAROUSEL_ACTIVE_ITEM":
77612
+ this.carouselStates[cmd.figureId] = cmd.item;
77613
+ break;
77614
+ case "DELETE_FIGURE":
77615
+ delete this.carouselStates[cmd.figureId];
77616
+ break;
77617
+ case "UPDATE_CAROUSEL":
77618
+ this.fixWrongCarouselState(cmd.figureId);
77619
+ break;
77620
+ case "DELETE_CHART":
77621
+ case "UNDO":
77622
+ case "REDO":
77623
+ case "DELETE_SHEET":
77624
+ for (const figureId in this.carouselStates) {
77625
+ this.fixWrongCarouselState(figureId);
77626
+ }
77627
+ break;
77628
+ }
77629
+ }
77630
+ getSelectedCarouselItem(figureId) {
77631
+ const carousel = this.getters.getCarousel(figureId);
77632
+ if (!carousel.items.length) {
77633
+ return undefined;
77634
+ }
77635
+ return this.carouselStates[figureId] ? this.carouselStates[figureId] : carousel.items[0];
77636
+ }
77637
+ getChartFromFigureId(figureId) {
77638
+ const sheetId = this.getters.getFigureSheetId(figureId);
77639
+ if (!sheetId) {
77640
+ return undefined;
77641
+ }
77642
+ const chartId = this.getChartIdFromFigureId(figureId);
77643
+ return chartId ? this.getters.getChart(chartId) : undefined;
77644
+ }
77645
+ getChartIdFromFigureId(figureId) {
77646
+ const sheetId = this.getters.getFigureSheetId(figureId);
77647
+ if (!sheetId) {
77648
+ return undefined;
77649
+ }
77650
+ const figure = this.getters.getFigure(sheetId, figureId);
77651
+ if (!figure || (figure.tag !== "chart" && figure.tag !== "carousel")) {
77652
+ return undefined;
77653
+ }
77654
+ if (figure.tag === "carousel") {
77655
+ const carouselItem = this.getSelectedCarouselItem(figureId);
77656
+ return carouselItem?.type === "chart" ? carouselItem.chartId : undefined;
77657
+ }
77658
+ return this.getters
77659
+ .getChartIds(sheetId)
77660
+ .find((chartId) => this.getters.getFigureIdFromChartId(chartId) === figureId);
77661
+ }
77662
+ fixWrongCarouselState(figureId) {
77663
+ if (!this.getters.doesCarouselExist(figureId)) {
77664
+ delete this.carouselStates[figureId];
77665
+ return;
77666
+ }
77667
+ const carousel = this.getters.getCarousel(figureId);
77668
+ if (!carousel) {
77669
+ delete this.carouselStates[figureId];
77670
+ return;
77671
+ }
77672
+ if (!this.carouselStates[figureId]) {
77673
+ this.carouselStates[figureId] = carousel.items[0];
77674
+ }
77675
+ else if (carousel.items.length === 0) {
77676
+ delete this.carouselStates[figureId];
77677
+ }
77678
+ else if (!carousel.items.some((item) => deepEquals(item, this.carouselStates[figureId]))) {
77679
+ this.carouselStates[figureId] = carousel.items[0];
77680
+ }
77681
+ }
77682
+ addNewChartToCarousel(figureId, sheetId) {
77683
+ const carousel = this.getters.getCarousel(figureId);
77684
+ const chartId = this.uuidGenerator.smallUuid();
77685
+ this.dispatch("CREATE_CHART", {
77686
+ chartId,
77687
+ figureId,
77688
+ sheetId,
77689
+ definition: CAROUSEL_DEFAULT_CHART_DEFINITION,
77690
+ });
77691
+ const definition = { items: [...carousel.items, { type: "chart", chartId }] };
77692
+ this.dispatch("UPDATE_CAROUSEL", { sheetId, figureId, definition });
77693
+ }
77694
+ addFigureChartToCarousel(figureId, chartFigureId, sheetId) {
77695
+ const chartId = this.getChartIdFromFigureId(chartFigureId);
77696
+ if (!chartId) {
77697
+ return;
77698
+ }
77699
+ const carousel = this.getters.getCarousel(figureId);
77700
+ const definition = { items: [...carousel.items, { type: "chart", chartId }] };
77701
+ this.dispatch("UPDATE_CAROUSEL", { sheetId, figureId, definition });
77702
+ this.dispatch("UPDATE_CHART", {
77703
+ sheetId,
77704
+ chartId,
77705
+ figureId,
77706
+ definition: this.getters.getChartDefinition(chartId),
77707
+ });
77708
+ this.dispatch("DELETE_FIGURE", { sheetId, figureId: chartFigureId });
77709
+ }
77710
+ }
77711
+
76198
77712
  class HeaderPositionsUIPlugin extends UIPlugin {
76199
77713
  static getters = ["getColDimensions", "getRowDimensions", "getColRowOffset"];
76200
77714
  headerPositions = {};
@@ -76326,6 +77840,7 @@ stores.inject(MyMetaStore, storeInstance);
76326
77840
  .add("conditional formatting", ConditionalFormatPlugin)
76327
77841
  .add("figures", FigurePlugin)
76328
77842
  .add("chart", ChartPlugin)
77843
+ .add("carousel", CarouselPlugin)
76329
77844
  .add("image", ImagePlugin)
76330
77845
  .add("pivot_core", PivotCorePlugin)
76331
77846
  .add("spreadsheet_pivot_core", SpreadsheetPivotCorePlugin)
@@ -76358,7 +77873,8 @@ stores.inject(MyMetaStore, storeInstance);
76358
77873
  .add("table_computed_style", TableComputedStylePlugin)
76359
77874
  .add("header_positions", HeaderPositionsUIPlugin)
76360
77875
  .add("viewport", SheetViewPlugin)
76361
- .add("clipboard", ClipboardPlugin);
77876
+ .add("clipboard", ClipboardPlugin)
77877
+ .add("carousel_ui", CarouselUIPlugin);
76362
77878
  // Plugins which have a derived state from core data
76363
77879
  const coreViewsPluginRegistry = new Registry()
76364
77880
  .add("evaluation", EvaluationPlugin)
@@ -77311,6 +78827,10 @@ stores.inject(MyMetaStore, storeInstance);
77311
78827
  .addChild("insert_chart", ["insert"], {
77312
78828
  ...insertChart,
77313
78829
  sequence: 50,
78830
+ })
78831
+ .addChild("insert_carousel", ["insert"], {
78832
+ ...insertCarousel,
78833
+ sequence: 51,
77314
78834
  })
77315
78835
  .addChild("insert_pivot", ["insert"], {
77316
78836
  ...insertPivot,
@@ -78481,9 +80001,7 @@ stores.inject(MyMetaStore, storeInstance);
78481
80001
  }
78482
80002
  if (!(xc in clickableCells[sheetId])) {
78483
80003
  const clickableCell = this.findClickableItem(position);
78484
- if (clickableCell) {
78485
- clickableCells[sheetId][xc] = clickableCell;
78486
- }
80004
+ clickableCells[sheetId][xc] = clickableCell;
78487
80005
  }
78488
80006
  return clickableCells[sheetId][xc];
78489
80007
  }
@@ -80725,6 +82243,7 @@ stores.inject(MyMetaStore, storeInstance);
80725
82243
  loadLocales: this.model.config.external.loadLocales,
80726
82244
  isDashboard: () => this.model.getters.isDashboard(),
80727
82245
  openSidePanel: this.sidePanel.open.bind(this.sidePanel),
82246
+ replaceSidePanel: this.sidePanel.replace.bind(this.sidePanel),
80728
82247
  toggleSidePanel: this.sidePanel.toggle.bind(this.sidePanel),
80729
82248
  clipboard: this.env.clipboard || instantiateClipboard(),
80730
82249
  startCellEdition: (content) => this.composerFocusStore.focusActiveComposer({ content }),
@@ -83693,7 +85212,7 @@ stores.inject(MyMetaStore, storeInstance);
83693
85212
  ];
83694
85213
  }
83695
85214
 
83696
- function createDrawing(drawingRelIds, sheet, figures) {
85215
+ function createDrawing(drawingRelIds, sheet, figures, construct) {
83697
85216
  const namespaces = [
83698
85217
  ["xmlns:xdr", NAMESPACE.drawing],
83699
85218
  ["xmlns:r", RELATIONSHIP_NSR],
@@ -83704,7 +85223,7 @@ stores.inject(MyMetaStore, storeInstance);
83704
85223
  for (const [figureIndex, figure] of Object.entries(figures)) {
83705
85224
  switch (figure?.tag) {
83706
85225
  case "chart":
83707
- figuresNodes.push(createChartDrawing(figure, sheet, drawingRelIds[figureIndex]));
85226
+ figuresNodes.push(createChartDrawing(figure, sheet, drawingRelIds[figureIndex], construct));
83708
85227
  break;
83709
85228
  case "image":
83710
85229
  figuresNodes.push(createImageDrawing(figure, sheet, drawingRelIds[figureIndex]));
@@ -83766,10 +85285,10 @@ stores.inject(MyMetaStore, storeInstance);
83766
85285
  offset: convertDotValueToEMU(offset - currentPosition + FIGURE_BORDER_WIDTH),
83767
85286
  };
83768
85287
  }
83769
- function createChartDrawing(figure, sheet, chartRelId) {
85288
+ function createChartDrawing(figure, sheet, chartRelId, construct) {
83770
85289
  // position
83771
85290
  const { from, to } = convertFigureData(figure, sheet);
83772
- const chartId = convertChartId(figure.id);
85291
+ const chartId = convertChartId(figure.id, construct);
83773
85292
  const cNvPrAttrs = [
83774
85293
  ["id", chartId],
83775
85294
  ["name", `Chart ${chartId}`],
@@ -84432,7 +85951,7 @@ stores.inject(MyMetaStore, storeInstance);
84432
85951
  let drawingNode = escapeXml ``;
84433
85952
  const drawingRelIds = [];
84434
85953
  for (const chart of sheet.charts) {
84435
- const xlsxChartId = convertChartId(chart.id);
85954
+ const xlsxChartId = convertChartId(chart.id, construct);
84436
85955
  const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
84437
85956
  target: `../charts/chart${xlsxChartId}.xml`,
84438
85957
  type: XLSX_RELATION_TYPE.chart,
@@ -84466,7 +85985,7 @@ stores.inject(MyMetaStore, storeInstance);
84466
85985
  target: `../drawings/drawing${sheetIndex}.xml`,
84467
85986
  type: XLSX_RELATION_TYPE.drawing,
84468
85987
  });
84469
- files.push(createXMLFile(createDrawing(drawingRelIds, sheet, drawings), `xl/drawings/drawing${sheetIndex}.xml`, "drawing"));
85988
+ files.push(createXMLFile(createDrawing(drawingRelIds, sheet, drawings, construct), `xl/drawings/drawing${sheetIndex}.xml`, "drawing"));
84470
85989
  drawingNode = escapeXml /*xml*/ `<drawing r:id="${drawingRelId}" />`;
84471
85990
  }
84472
85991
  const sheetXml = escapeXml /*xml*/ `
@@ -85367,6 +86886,7 @@ stores.inject(MyMetaStore, storeInstance);
85367
86886
  ChartPanel,
85368
86887
  ChartFigure,
85369
86888
  ChartJsComponent,
86889
+ ZoomableChartJsComponent,
85370
86890
  Grid,
85371
86891
  GridOverlay,
85372
86892
  ScorecardChart,
@@ -85375,6 +86895,7 @@ stores.inject(MyMetaStore, storeInstance);
85375
86895
  PieChartDesignPanel,
85376
86896
  GenericChartConfigPanel,
85377
86897
  ChartWithAxisDesignPanel,
86898
+ GenericZoomableChartDesignPanel,
85378
86899
  LineChartDesignPanel,
85379
86900
  GaugeChartConfigPanel,
85380
86901
  GaugeChartDesignPanel,
@@ -85449,6 +86970,7 @@ stores.inject(MyMetaStore, storeInstance);
85449
86970
  HIGHLIGHT_COLOR,
85450
86971
  PIVOT_TABLE_CONFIG,
85451
86972
  ChartTerms,
86973
+ FIGURE_ID_SPLITTER,
85452
86974
  };
85453
86975
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
85454
86976
 
@@ -85503,9 +87025,9 @@ stores.inject(MyMetaStore, storeInstance);
85503
87025
  exports.tokenize = tokenize;
85504
87026
 
85505
87027
 
85506
- __info__.version = "18.5.0-alpha.6";
85507
- __info__.date = "2025-08-05T06:27:49.505Z";
85508
- __info__.hash = "67e091f";
87028
+ __info__.version = "18.5.0-alpha.8";
87029
+ __info__.date = "2025-08-18T08:17:58.775Z";
87030
+ __info__.hash = "994f1cb";
85509
87031
 
85510
87032
 
85511
87033
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);