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