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