@odoo/o-spreadsheet 18.5.0-alpha.7 → 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.7
6
- * @date 2025-08-08T11:00:44.117Z
7
- * @hash 1019d55
5
+ * @version 18.5.0-alpha.8
6
+ * @date 2025-08-18T08:17:58.775Z
7
+ * @hash 994f1cb
8
8
  */
9
9
 
10
10
  'use strict';
@@ -200,6 +200,7 @@ const CHART_PADDING_BOTTOM = 10;
200
200
  const CHART_PADDING_TOP = 15;
201
201
  const CHART_TITLE_FONT_SIZE = 16;
202
202
  const CHART_AXIS_TITLE_FONT_SIZE = 12;
203
+ const MASTER_CHART_HEIGHT = 60;
203
204
  const SCORECARD_CHART_TITLE_FONT_SIZE = 14;
204
205
  const PIVOT_TOKEN_COLOR = "#F28C28";
205
206
  // Color picker defaults as upper case HEX to match `toHex`helper
@@ -415,6 +416,7 @@ const PIVOT_TABLE_CONFIG = {
415
416
  };
416
417
  const PIVOT_INDENT = 15;
417
418
  const PIVOT_COLLAPSE_ICON_SIZE = 12;
419
+ const PIVOT_MAX_NUMBER_OF_CELLS = 1e5;
418
420
  const DEFAULT_CURRENCY = {
419
421
  symbol: "$",
420
422
  position: "before",
@@ -19036,6 +19038,211 @@ var logical = /*#__PURE__*/Object.freeze({
19036
19038
  XOR: XOR
19037
19039
  });
19038
19040
 
19041
+ const CfTerms = {
19042
+ Errors: {
19043
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
19044
+ ["FirstArgMissing" /* CommandResult.FirstArgMissing */]: _t("The argument is missing. Please provide a value"),
19045
+ ["SecondArgMissing" /* CommandResult.SecondArgMissing */]: _t("The second argument is missing. Please provide a value"),
19046
+ ["MinNaN" /* CommandResult.MinNaN */]: _t("The minpoint must be a number"),
19047
+ ["MidNaN" /* CommandResult.MidNaN */]: _t("The midpoint must be a number"),
19048
+ ["MaxNaN" /* CommandResult.MaxNaN */]: _t("The maxpoint must be a number"),
19049
+ ["ValueUpperInflectionNaN" /* CommandResult.ValueUpperInflectionNaN */]: _t("The first value must be a number"),
19050
+ ["ValueLowerInflectionNaN" /* CommandResult.ValueLowerInflectionNaN */]: _t("The second value must be a number"),
19051
+ ["MinBiggerThanMax" /* CommandResult.MinBiggerThanMax */]: _t("Minimum must be smaller then Maximum"),
19052
+ ["MinBiggerThanMid" /* CommandResult.MinBiggerThanMid */]: _t("Minimum must be smaller then Midpoint"),
19053
+ ["MidBiggerThanMax" /* CommandResult.MidBiggerThanMax */]: _t("Midpoint must be smaller then Maximum"),
19054
+ ["LowerBiggerThanUpper" /* CommandResult.LowerBiggerThanUpper */]: _t("Lower inflection point must be smaller than upper inflection point"),
19055
+ ["MinInvalidFormula" /* CommandResult.MinInvalidFormula */]: _t("Invalid Minpoint formula"),
19056
+ ["MaxInvalidFormula" /* CommandResult.MaxInvalidFormula */]: _t("Invalid Maxpoint formula"),
19057
+ ["MidInvalidFormula" /* CommandResult.MidInvalidFormula */]: _t("Invalid Midpoint formula"),
19058
+ ["ValueUpperInvalidFormula" /* CommandResult.ValueUpperInvalidFormula */]: _t("Invalid upper inflection point formula"),
19059
+ ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
19060
+ ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
19061
+ ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
19062
+ Unexpected: _t("The rule is invalid for an unknown reason"),
19063
+ },
19064
+ ColorScale: _t("Color scale"),
19065
+ IconSet: _t("Icon set"),
19066
+ DataBar: _t("Data bar"),
19067
+ };
19068
+ const ChartTerms = {
19069
+ Series: _t("Series"),
19070
+ BackgroundColor: _t("Background color"),
19071
+ StackedBarChart: _t("Stacked bar chart"),
19072
+ StackedLineChart: _t("Stacked line chart"),
19073
+ StackedAreaChart: _t("Stacked area chart"),
19074
+ StackedColumnChart: _t("Stacked column chart"),
19075
+ CumulativeData: _t("Cumulative data"),
19076
+ TreatLabelsAsText: _t("Treat labels as text"),
19077
+ AggregatedChart: _t("Aggregate"),
19078
+ Errors: {
19079
+ Unexpected: _t("The chart definition is invalid for an unknown reason"),
19080
+ // BASIC CHART ERRORS (LINE | BAR | PIE)
19081
+ ["InvalidDataSet" /* CommandResult.InvalidDataSet */]: _t("The dataset is invalid"),
19082
+ ["InvalidLabelRange" /* CommandResult.InvalidLabelRange */]: _t("Labels are invalid"),
19083
+ // SCORECARD CHART ERRORS
19084
+ ["InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */]: _t("The key value is invalid"),
19085
+ ["InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */]: _t("The baseline value is invalid"),
19086
+ // GAUGE CHART ERRORS
19087
+ ["InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */]: _t("The data range is invalid"),
19088
+ ["EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */]: _t("A minimum range limit value is needed"),
19089
+ ["GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */]: _t("The minimum range limit value must be a number"),
19090
+ ["EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */]: _t("A maximum range limit value is needed"),
19091
+ ["GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */]: _t("The maximum range limit value must be a number"),
19092
+ ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
19093
+ ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
19094
+ },
19095
+ GeoChart: {
19096
+ ColorScales: {
19097
+ blues: _t("Blues"),
19098
+ cividis: _t("Cividis"),
19099
+ greens: _t("Greens"),
19100
+ greys: _t("Greys"),
19101
+ oranges: _t("Oranges"),
19102
+ purples: _t("Purples"),
19103
+ rainbow: _t("Rainbow"),
19104
+ reds: _t("Reds"),
19105
+ viridis: _t("Viridis"),
19106
+ },
19107
+ },
19108
+ };
19109
+ const CustomCurrencyTerms = {
19110
+ Custom: _t("Custom"),
19111
+ };
19112
+ const MergeErrorMessage = _t("Merged cells are preventing this operation. Unmerge those cells and try again.");
19113
+ const SplitToColumnsTerms = {
19114
+ Errors: {
19115
+ Unexpected: _t("Cannot split the selection for an unknown reason"),
19116
+ ["NoSplitSeparatorInSelection" /* CommandResult.NoSplitSeparatorInSelection */]: _t("There is no match for the selected separator in the selection"),
19117
+ ["MoreThanOneColumnSelected" /* CommandResult.MoreThanOneColumnSelected */]: _t("Only a selection from a single column can be split"),
19118
+ ["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]: _t("Splitting will overwrite existing content"),
19119
+ },
19120
+ };
19121
+ const RemoveDuplicateTerms = {
19122
+ Errors: {
19123
+ Unexpected: _t("Cannot remove duplicates for an unknown reason"),
19124
+ ["MoreThanOneRangeSelected" /* CommandResult.MoreThanOneRangeSelected */]: _t("Please select only one range of cells"),
19125
+ ["EmptyTarget" /* CommandResult.EmptyTarget */]: _t("Please select a range of cells containing values."),
19126
+ ["NoColumnsProvided" /* CommandResult.NoColumnsProvided */]: _t("Please select at latest one column to analyze."),
19127
+ //TODO: Remove it when accept to copy and paste merge cells
19128
+ ["WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */]: _t("This operation is not possible due to a merge. Please remove the merges first than try again."),
19129
+ },
19130
+ };
19131
+ const DVTerms = {
19132
+ DateIs: {
19133
+ today: _t("today"),
19134
+ yesterday: _t("yesterday"),
19135
+ tomorrow: _t("tomorrow"),
19136
+ lastWeek: _t("in the past week"),
19137
+ lastMonth: _t("in the past month"),
19138
+ lastYear: _t("in the past year"),
19139
+ },
19140
+ DateIsBefore: {
19141
+ today: _t("today"),
19142
+ yesterday: _t("yesterday"),
19143
+ tomorrow: _t("tomorrow"),
19144
+ lastWeek: _t("one week ago"),
19145
+ lastMonth: _t("one month ago"),
19146
+ lastYear: _t("one year ago"),
19147
+ },
19148
+ CriterionError: {
19149
+ notEmptyValue: _t("The value must not be empty"),
19150
+ numberValue: _t("The value must be a number"),
19151
+ dateValue: _t("The value must be a date"),
19152
+ validRange: _t("The value must be a valid range"),
19153
+ validFormula: _t("The formula must be valid"),
19154
+ },
19155
+ Errors: {
19156
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
19157
+ ["InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */]: _t("One or more of the provided criteria values are invalid. Please review and correct them."),
19158
+ ["InvalidNumberOfCriterionValues" /* CommandResult.InvalidNumberOfCriterionValues */]: _t("One or more of the provided criteria values are missing."),
19159
+ Unexpected: _t("The rule is invalid for an unknown reason."),
19160
+ },
19161
+ };
19162
+ const TableTerms = {
19163
+ Errors: {
19164
+ Unexpected: _t("The table zone is invalid for an unknown reason"),
19165
+ ["TableOverlap" /* CommandResult.TableOverlap */]: _t("You cannot create overlapping tables."),
19166
+ ["NonContinuousTargets" /* CommandResult.NonContinuousTargets */]: _t("A table can only be created on a continuous selection."),
19167
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
19168
+ ["TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */]: _t("The range is out of the sheet"),
19169
+ },
19170
+ Checkboxes: {
19171
+ hasFilters: _t("Filter button"),
19172
+ headerRow: _t("Header row(s)"),
19173
+ bandedRows: _t("Banded rows"),
19174
+ firstColumn: _t("First column"),
19175
+ lastColumn: _t("Last column"),
19176
+ bandedColumns: _t("Banded columns"),
19177
+ automaticAutofill: _t("Automatically autofill formulas"),
19178
+ totalRow: _t("Total row"),
19179
+ isDynamic: _t("Auto-adjust to formula result"),
19180
+ },
19181
+ Tooltips: {
19182
+ filterWithoutHeader: _t("Cannot have filters without a header row"),
19183
+ isDynamic: _t("For tables based on array formulas only"),
19184
+ },
19185
+ };
19186
+ const measureDisplayTerms = {
19187
+ labels: {
19188
+ no_calculations: _t("No calculations"),
19189
+ "%_of_grand_total": _t("% of grand total"),
19190
+ "%_of_col_total": _t("% of column total"),
19191
+ "%_of_row_total": _t("% of row total"),
19192
+ "%_of": _t("% of"),
19193
+ "%_of_parent_row_total": _t("% of parent row total"),
19194
+ "%_of_parent_col_total": _t("% of parent column total"),
19195
+ "%_of_parent_total": _t("% of parent total"),
19196
+ difference_from: _t("Difference from"),
19197
+ "%_difference_from": _t("% difference from"),
19198
+ running_total: _t("Running total"),
19199
+ "%_running_total": _t("% Running total"),
19200
+ rank_asc: _t("Rank smallest to largest"),
19201
+ rank_desc: _t("Rank largest to smallest"),
19202
+ index: _t("Index"),
19203
+ },
19204
+ descriptions: {
19205
+ "%_of_grand_total": () => _t("Displayed as % of grand total"),
19206
+ "%_of_col_total": () => _t("Displayed as % of column total"),
19207
+ "%_of_row_total": () => _t("Displayed as % of row total"),
19208
+ "%_of": (field) => _t('Displayed as % of "%s"', field),
19209
+ "%_of_parent_row_total": (field) => _t('Displayed as % of parent row total of "%s"', field),
19210
+ "%_of_parent_col_total": () => _t("Displayed as % of parent column total"),
19211
+ "%_of_parent_total": (field) => _t('Displayed as % of parent "%s" total', field),
19212
+ difference_from: (field) => _t('Displayed as difference from "%s"', field),
19213
+ "%_difference_from": (field) => _t('Displayed as % difference from "%s"', field),
19214
+ running_total: (field) => _t('Displayed as running total based on "%s"', field),
19215
+ "%_running_total": (field) => _t('Displayed as % running total based on "%s"', field),
19216
+ rank_asc: (field) => _t('Displayed as rank from smallest to largest based on "%s"', field),
19217
+ rank_desc: (field) => _t('Displayed as rank largest to smallest based on "%s"', field),
19218
+ index: () => _t("Displayed as index"),
19219
+ },
19220
+ documentation: {
19221
+ no_calculations: _t("Displays the value that is entered in the field."),
19222
+ "%_of_grand_total": _t("Displays values as a percentage of the grand total of all the values or data points in the report."),
19223
+ "%_of_col_total": _t("Displays all the values in each column or series as a percentage of the total for the column or series."),
19224
+ "%_of_row_total": _t("Displays the value in each row or category as a percentage of the total for the row or category."),
19225
+ "%_of": _t("Displays values as a percentage of the value of the Base item in the Base field."),
19226
+ "%_of_parent_row_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on rows)"),
19227
+ "%_of_parent_col_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on columns)"),
19228
+ "%_of_parent_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item of the selected Base field)"),
19229
+ difference_from: _t("Displays values as the difference from the value of the Base item in the Base field."),
19230
+ "%_difference_from": _t("Displays values as the percentage difference from the value of the Base item in the Base field."),
19231
+ running_total: _t("Displays the value for successive items in the Base field as a running total."),
19232
+ "%_running_total": _t("Calculates the value as a percentage for successive items in the Base field that are displayed as a running total."),
19233
+ rank_asc: _t("Displays the rank of selected values in a specific field, listing the smallest item in the field as 1, and each larger value with a higher rank value."),
19234
+ rank_desc: _t("Displays the rank of selected values in a specific field, listing the largest item in the field as 1, and each smaller value with a higher rank value."),
19235
+ index: _t("Calculates values as follows:\n((value in cell) x (Grand Total of Grand Totals)) / ((Grand Row Total) x (Grand Column Total))"),
19236
+ },
19237
+ };
19238
+ function getPivotTooBigErrorMessage(numberOfCells, locale) {
19239
+ const formattedNumber = formatValue(numberOfCells, {
19240
+ format: "0,00",
19241
+ locale: locale,
19242
+ });
19243
+ return _t("Oops—this pivot table is quite large (%s cells). Try simplifying it using the side panel.", formattedNumber);
19244
+ }
19245
+
19039
19246
  /**
19040
19247
  * Get the pivot ID from the formula pivot ID.
19041
19248
  */
@@ -19646,6 +19853,9 @@ const PIVOT = {
19646
19853
  return error;
19647
19854
  }
19648
19855
  const table = pivot.getCollapsedTableStructure();
19856
+ if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
19857
+ return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
19858
+ }
19649
19859
  const cells = table.getPivotCells(visibilityOptions);
19650
19860
  let headerRows = 0;
19651
19861
  if (visibilityOptions.displayColumnHeaders) {
@@ -19744,7 +19954,7 @@ const OFFSET = {
19744
19954
  right: startingCol + offsetWidth - 1,
19745
19955
  bottom: startingRow + offsetHeight - 1,
19746
19956
  };
19747
- const range = this.getters.getRangeFromZone(this.__originSheetId, dependencyZone);
19957
+ const range = this.getters.getRangeFromZone(sheetId, dependencyZone);
19748
19958
  if (range.invalidXc || range.invalidSheetName) {
19749
19959
  return new InvalidReferenceError();
19750
19960
  }
@@ -22027,12 +22237,12 @@ function getPieColors(colors, dataSetsValues) {
22027
22237
  }
22028
22238
  return pieColors;
22029
22239
  }
22030
- function truncateLabel(label) {
22240
+ function truncateLabel(label, maxLen = MAX_CHAR_LABEL) {
22031
22241
  if (!label) {
22032
22242
  return "";
22033
22243
  }
22034
- if (label.length > MAX_CHAR_LABEL) {
22035
- return label.substring(0, MAX_CHAR_LABEL) + "…";
22244
+ if (label.length > maxLen) {
22245
+ return label.substring(0, maxLen) + "…";
22036
22246
  }
22037
22247
  return label;
22038
22248
  }
@@ -23034,29 +23244,36 @@ class ChartJsComponent extends owl.Component {
23034
23244
  const runtime = this.chartRuntime;
23035
23245
  this.currentRuntime = runtime;
23036
23246
  // Note: chartJS modify the runtime in place, so it's important to give it a copy
23037
- this.createChart(deepCopy(runtime.chartJsConfig));
23247
+ this.createChart(deepCopy(runtime));
23038
23248
  });
23039
- owl.onWillUnmount(() => this.chart?.destroy());
23249
+ owl.onWillUnmount(this.unmount.bind(this));
23040
23250
  owl.useEffect(() => {
23041
23251
  const runtime = this.chartRuntime;
23042
23252
  if (runtime !== this.currentRuntime) {
23043
23253
  if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
23044
23254
  this.chart?.destroy();
23045
- this.createChart(deepCopy(runtime.chartJsConfig));
23255
+ this.createChart(deepCopy(runtime));
23046
23256
  }
23047
23257
  else {
23048
- this.updateChartJs(deepCopy(runtime.chartJsConfig));
23258
+ this.updateChartJs(deepCopy(runtime));
23049
23259
  }
23050
23260
  this.currentRuntime = runtime;
23051
23261
  }
23052
23262
  else if (this.currentDevicePixelRatio !== window.devicePixelRatio) {
23053
23263
  this.currentDevicePixelRatio = window.devicePixelRatio;
23054
- this.updateChartJs(deepCopy(this.currentRuntime.chartJsConfig));
23264
+ this.updateChartJs(deepCopy(this.currentRuntime));
23055
23265
  }
23056
23266
  });
23057
23267
  }
23058
- createChart(chartData) {
23059
- if (this.env.model.getters.isDashboard() && this.animationStore) {
23268
+ unmount() {
23269
+ this.chart?.destroy();
23270
+ }
23271
+ get shouldAnimate() {
23272
+ return this.env.model.getters.isDashboard();
23273
+ }
23274
+ createChart(chartRuntime) {
23275
+ let chartData = chartRuntime.chartJsConfig;
23276
+ if (this.shouldAnimate && this.animationStore) {
23060
23277
  const chartType = this.env.model.getters.getChart(this.props.chartId)?.type;
23061
23278
  if (chartType && this.animationStore.animationPlayed[this.animationFigureId] !== chartType) {
23062
23279
  chartData = this.enableAnimationInChartData(chartData);
@@ -23067,8 +23284,9 @@ class ChartJsComponent extends owl.Component {
23067
23284
  const ctx = canvas.getContext("2d");
23068
23285
  this.chart = new window.Chart(ctx, chartData);
23069
23286
  }
23070
- updateChartJs(chartData) {
23071
- if (this.env.model.getters.isDashboard()) {
23287
+ updateChartJs(chartRuntime) {
23288
+ let chartData = chartRuntime.chartJsConfig;
23289
+ if (this.shouldAnimate) {
23072
23290
  const chartType = this.env.model.getters.getChart(this.props.chartId)?.type;
23073
23291
  if (chartType && this.hasChartDataChanged() && this.animationStore) {
23074
23292
  chartData = this.enableAnimationInChartData(chartData);
@@ -24475,204 +24693,6 @@ const backgroundColorChartJSPlugin = {
24475
24693
  },
24476
24694
  };
24477
24695
 
24478
- const CfTerms = {
24479
- Errors: {
24480
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
24481
- ["FirstArgMissing" /* CommandResult.FirstArgMissing */]: _t("The argument is missing. Please provide a value"),
24482
- ["SecondArgMissing" /* CommandResult.SecondArgMissing */]: _t("The second argument is missing. Please provide a value"),
24483
- ["MinNaN" /* CommandResult.MinNaN */]: _t("The minpoint must be a number"),
24484
- ["MidNaN" /* CommandResult.MidNaN */]: _t("The midpoint must be a number"),
24485
- ["MaxNaN" /* CommandResult.MaxNaN */]: _t("The maxpoint must be a number"),
24486
- ["ValueUpperInflectionNaN" /* CommandResult.ValueUpperInflectionNaN */]: _t("The first value must be a number"),
24487
- ["ValueLowerInflectionNaN" /* CommandResult.ValueLowerInflectionNaN */]: _t("The second value must be a number"),
24488
- ["MinBiggerThanMax" /* CommandResult.MinBiggerThanMax */]: _t("Minimum must be smaller then Maximum"),
24489
- ["MinBiggerThanMid" /* CommandResult.MinBiggerThanMid */]: _t("Minimum must be smaller then Midpoint"),
24490
- ["MidBiggerThanMax" /* CommandResult.MidBiggerThanMax */]: _t("Midpoint must be smaller then Maximum"),
24491
- ["LowerBiggerThanUpper" /* CommandResult.LowerBiggerThanUpper */]: _t("Lower inflection point must be smaller than upper inflection point"),
24492
- ["MinInvalidFormula" /* CommandResult.MinInvalidFormula */]: _t("Invalid Minpoint formula"),
24493
- ["MaxInvalidFormula" /* CommandResult.MaxInvalidFormula */]: _t("Invalid Maxpoint formula"),
24494
- ["MidInvalidFormula" /* CommandResult.MidInvalidFormula */]: _t("Invalid Midpoint formula"),
24495
- ["ValueUpperInvalidFormula" /* CommandResult.ValueUpperInvalidFormula */]: _t("Invalid upper inflection point formula"),
24496
- ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
24497
- ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
24498
- ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
24499
- Unexpected: _t("The rule is invalid for an unknown reason"),
24500
- },
24501
- ColorScale: _t("Color scale"),
24502
- IconSet: _t("Icon set"),
24503
- DataBar: _t("Data bar"),
24504
- };
24505
- const ChartTerms = {
24506
- Series: _t("Series"),
24507
- BackgroundColor: _t("Background color"),
24508
- StackedBarChart: _t("Stacked bar chart"),
24509
- StackedLineChart: _t("Stacked line chart"),
24510
- StackedAreaChart: _t("Stacked area chart"),
24511
- StackedColumnChart: _t("Stacked column chart"),
24512
- CumulativeData: _t("Cumulative data"),
24513
- TreatLabelsAsText: _t("Treat labels as text"),
24514
- AggregatedChart: _t("Aggregate"),
24515
- Errors: {
24516
- Unexpected: _t("The chart definition is invalid for an unknown reason"),
24517
- // BASIC CHART ERRORS (LINE | BAR | PIE)
24518
- ["InvalidDataSet" /* CommandResult.InvalidDataSet */]: _t("The dataset is invalid"),
24519
- ["InvalidLabelRange" /* CommandResult.InvalidLabelRange */]: _t("Labels are invalid"),
24520
- // SCORECARD CHART ERRORS
24521
- ["InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */]: _t("The key value is invalid"),
24522
- ["InvalidScorecardBaseline" /* CommandResult.InvalidScorecardBaseline */]: _t("The baseline value is invalid"),
24523
- // GAUGE CHART ERRORS
24524
- ["InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */]: _t("The data range is invalid"),
24525
- ["EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */]: _t("A minimum range limit value is needed"),
24526
- ["GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */]: _t("The minimum range limit value must be a number"),
24527
- ["EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */]: _t("A maximum range limit value is needed"),
24528
- ["GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */]: _t("The maximum range limit value must be a number"),
24529
- ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
24530
- ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
24531
- },
24532
- GeoChart: {
24533
- ColorScales: {
24534
- blues: _t("Blues"),
24535
- cividis: _t("Cividis"),
24536
- greens: _t("Greens"),
24537
- greys: _t("Greys"),
24538
- oranges: _t("Oranges"),
24539
- purples: _t("Purples"),
24540
- rainbow: _t("Rainbow"),
24541
- reds: _t("Reds"),
24542
- viridis: _t("Viridis"),
24543
- },
24544
- },
24545
- };
24546
- const CustomCurrencyTerms = {
24547
- Custom: _t("Custom"),
24548
- };
24549
- const MergeErrorMessage = _t("Merged cells are preventing this operation. Unmerge those cells and try again.");
24550
- const SplitToColumnsTerms = {
24551
- Errors: {
24552
- Unexpected: _t("Cannot split the selection for an unknown reason"),
24553
- ["NoSplitSeparatorInSelection" /* CommandResult.NoSplitSeparatorInSelection */]: _t("There is no match for the selected separator in the selection"),
24554
- ["MoreThanOneColumnSelected" /* CommandResult.MoreThanOneColumnSelected */]: _t("Only a selection from a single column can be split"),
24555
- ["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]: _t("Splitting will overwrite existing content"),
24556
- },
24557
- };
24558
- const RemoveDuplicateTerms = {
24559
- Errors: {
24560
- Unexpected: _t("Cannot remove duplicates for an unknown reason"),
24561
- ["MoreThanOneRangeSelected" /* CommandResult.MoreThanOneRangeSelected */]: _t("Please select only one range of cells"),
24562
- ["EmptyTarget" /* CommandResult.EmptyTarget */]: _t("Please select a range of cells containing values."),
24563
- ["NoColumnsProvided" /* CommandResult.NoColumnsProvided */]: _t("Please select at latest one column to analyze."),
24564
- //TODO: Remove it when accept to copy and paste merge cells
24565
- ["WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */]: _t("This operation is not possible due to a merge. Please remove the merges first than try again."),
24566
- },
24567
- };
24568
- const DVTerms = {
24569
- DateIs: {
24570
- today: _t("today"),
24571
- yesterday: _t("yesterday"),
24572
- tomorrow: _t("tomorrow"),
24573
- lastWeek: _t("in the past week"),
24574
- lastMonth: _t("in the past month"),
24575
- lastYear: _t("in the past year"),
24576
- },
24577
- DateIsBefore: {
24578
- today: _t("today"),
24579
- yesterday: _t("yesterday"),
24580
- tomorrow: _t("tomorrow"),
24581
- lastWeek: _t("one week ago"),
24582
- lastMonth: _t("one month ago"),
24583
- lastYear: _t("one year ago"),
24584
- },
24585
- CriterionError: {
24586
- notEmptyValue: _t("The value must not be empty"),
24587
- numberValue: _t("The value must be a number"),
24588
- dateValue: _t("The value must be a date"),
24589
- validRange: _t("The value must be a valid range"),
24590
- validFormula: _t("The formula must be valid"),
24591
- },
24592
- Errors: {
24593
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
24594
- ["InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */]: _t("One or more of the provided criteria values are invalid. Please review and correct them."),
24595
- ["InvalidNumberOfCriterionValues" /* CommandResult.InvalidNumberOfCriterionValues */]: _t("One or more of the provided criteria values are missing."),
24596
- Unexpected: _t("The rule is invalid for an unknown reason."),
24597
- },
24598
- };
24599
- const TableTerms = {
24600
- Errors: {
24601
- Unexpected: _t("The table zone is invalid for an unknown reason"),
24602
- ["TableOverlap" /* CommandResult.TableOverlap */]: _t("You cannot create overlapping tables."),
24603
- ["NonContinuousTargets" /* CommandResult.NonContinuousTargets */]: _t("A table can only be created on a continuous selection."),
24604
- ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid"),
24605
- ["TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */]: _t("The range is out of the sheet"),
24606
- },
24607
- Checkboxes: {
24608
- hasFilters: _t("Filter button"),
24609
- headerRow: _t("Header row(s)"),
24610
- bandedRows: _t("Banded rows"),
24611
- firstColumn: _t("First column"),
24612
- lastColumn: _t("Last column"),
24613
- bandedColumns: _t("Banded columns"),
24614
- automaticAutofill: _t("Automatically autofill formulas"),
24615
- totalRow: _t("Total row"),
24616
- isDynamic: _t("Auto-adjust to formula result"),
24617
- },
24618
- Tooltips: {
24619
- filterWithoutHeader: _t("Cannot have filters without a header row"),
24620
- isDynamic: _t("For tables based on array formulas only"),
24621
- },
24622
- };
24623
- const measureDisplayTerms = {
24624
- labels: {
24625
- no_calculations: _t("No calculations"),
24626
- "%_of_grand_total": _t("% of grand total"),
24627
- "%_of_col_total": _t("% of column total"),
24628
- "%_of_row_total": _t("% of row total"),
24629
- "%_of": _t("% of"),
24630
- "%_of_parent_row_total": _t("% of parent row total"),
24631
- "%_of_parent_col_total": _t("% of parent column total"),
24632
- "%_of_parent_total": _t("% of parent total"),
24633
- difference_from: _t("Difference from"),
24634
- "%_difference_from": _t("% difference from"),
24635
- running_total: _t("Running total"),
24636
- "%_running_total": _t("% Running total"),
24637
- rank_asc: _t("Rank smallest to largest"),
24638
- rank_desc: _t("Rank largest to smallest"),
24639
- index: _t("Index"),
24640
- },
24641
- descriptions: {
24642
- "%_of_grand_total": () => _t("Displayed as % of grand total"),
24643
- "%_of_col_total": () => _t("Displayed as % of column total"),
24644
- "%_of_row_total": () => _t("Displayed as % of row total"),
24645
- "%_of": (field) => _t('Displayed as % of "%s"', field),
24646
- "%_of_parent_row_total": (field) => _t('Displayed as % of parent row total of "%s"', field),
24647
- "%_of_parent_col_total": () => _t("Displayed as % of parent column total"),
24648
- "%_of_parent_total": (field) => _t('Displayed as % of parent "%s" total', field),
24649
- difference_from: (field) => _t('Displayed as difference from "%s"', field),
24650
- "%_difference_from": (field) => _t('Displayed as % difference from "%s"', field),
24651
- running_total: (field) => _t('Displayed as running total based on "%s"', field),
24652
- "%_running_total": (field) => _t('Displayed as % running total based on "%s"', field),
24653
- rank_asc: (field) => _t('Displayed as rank from smallest to largest based on "%s"', field),
24654
- rank_desc: (field) => _t('Displayed as rank largest to smallest based on "%s"', field),
24655
- index: () => _t("Displayed as index"),
24656
- },
24657
- documentation: {
24658
- no_calculations: _t("Displays the value that is entered in the field."),
24659
- "%_of_grand_total": _t("Displays values as a percentage of the grand total of all the values or data points in the report."),
24660
- "%_of_col_total": _t("Displays all the values in each column or series as a percentage of the total for the column or series."),
24661
- "%_of_row_total": _t("Displays the value in each row or category as a percentage of the total for the row or category."),
24662
- "%_of": _t("Displays values as a percentage of the value of the Base item in the Base field."),
24663
- "%_of_parent_row_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on rows)"),
24664
- "%_of_parent_col_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item on columns)"),
24665
- "%_of_parent_total": _t("Calculates values as follows:\n(value for the item) / (value for the parent item of the selected Base field)"),
24666
- difference_from: _t("Displays values as the difference from the value of the Base item in the Base field."),
24667
- "%_difference_from": _t("Displays values as the percentage difference from the value of the Base item in the Base field."),
24668
- running_total: _t("Displays the value for successive items in the Base field as a running total."),
24669
- "%_running_total": _t("Calculates the value as a percentage for successive items in the Base field that are displayed as a running total."),
24670
- 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."),
24671
- 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."),
24672
- index: _t("Calculates values as follows:\n((value in cell) x (Grand Total of Grand Totals)) / ((Grand Row Total) x (Grand Column Total))"),
24673
- },
24674
- };
24675
-
24676
24696
  const UNIT_LENGTH = {
24677
24697
  second: 1000,
24678
24698
  minute: 1000 * 60,
@@ -26021,7 +26041,7 @@ function getChartAxis(definition, position, type, options) {
26021
26041
  ticks: {
26022
26042
  padding: 5,
26023
26043
  color: fontColor,
26024
- callback: function (tickValue) {
26044
+ callback: function (tickValue, index, ticks) {
26025
26045
  // Category axis callback's internal tick value is the index of the label
26026
26046
  // https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
26027
26047
  return truncateLabel(this.getLabelForValue(tickValue));
@@ -26593,6 +26613,7 @@ class BarChart extends AbstractChart {
26593
26613
  axesDesign;
26594
26614
  horizontal;
26595
26615
  showValues;
26616
+ zoomable;
26596
26617
  constructor(definition, sheetId, getters) {
26597
26618
  super(definition, sheetId, getters);
26598
26619
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -26606,6 +26627,7 @@ class BarChart extends AbstractChart {
26606
26627
  this.axesDesign = definition.axesDesign;
26607
26628
  this.horizontal = definition.horizontal;
26608
26629
  this.showValues = definition.showValues;
26630
+ this.zoomable = definition.zoomable;
26609
26631
  }
26610
26632
  static transformDefinition(chartSheetId, definition, applyChange) {
26611
26633
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -26627,6 +26649,7 @@ class BarChart extends AbstractChart {
26627
26649
  axesDesign: context.axesDesign,
26628
26650
  showValues: context.showValues,
26629
26651
  horizontal: context.horizontal,
26652
+ zoomable: context.zoomable,
26630
26653
  };
26631
26654
  }
26632
26655
  getContextCreation() {
@@ -26681,6 +26704,7 @@ class BarChart extends AbstractChart {
26681
26704
  axesDesign: this.axesDesign,
26682
26705
  horizontal: this.horizontal,
26683
26706
  showValues: this.showValues,
26707
+ zoomable: this.horizontal ? undefined : this.zoomable,
26684
26708
  };
26685
26709
  }
26686
26710
  getDefinitionForExcel() {
@@ -26729,6 +26753,621 @@ function createBarChartRuntime(chart, getters) {
26729
26753
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
26730
26754
  }
26731
26755
 
26756
+ class FullScreenChartStore extends SpreadsheetStore {
26757
+ mutators = ["toggleFullScreenChart"];
26758
+ fullScreenFigure = undefined;
26759
+ toggleFullScreenChart(figureId) {
26760
+ if (this.fullScreenFigure?.id === figureId) {
26761
+ this.fullScreenFigure = undefined;
26762
+ }
26763
+ else {
26764
+ this.makeFullScreen(figureId);
26765
+ }
26766
+ }
26767
+ makeFullScreen(figureId) {
26768
+ const sheetId = this.getters.getActiveSheetId();
26769
+ const figure = this.getters.getFigure(sheetId, figureId);
26770
+ if (figure) {
26771
+ this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
26772
+ }
26773
+ }
26774
+ }
26775
+
26776
+ const TREND_LINE_AXES_IDS = [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID];
26777
+ const ZOOMABLE_AXIS_IDS = ["x", ...TREND_LINE_AXES_IDS];
26778
+ class ZoomableChartStore extends SpreadsheetStore {
26779
+ mutators = [
26780
+ "resetAxisLimits",
26781
+ "updateAxisLimits",
26782
+ "updateTrendLineConfiguration",
26783
+ "clearAxisLimits",
26784
+ ];
26785
+ originalAxisLimits = {};
26786
+ currentAxesLimits = {};
26787
+ idConversion = {};
26788
+ handle(cmd) {
26789
+ switch (cmd.type) {
26790
+ case "DELETE_FIGURE":
26791
+ if (cmd.figureId && this.idConversion[cmd.figureId]) {
26792
+ for (const chartId of this.idConversion[cmd.figureId]) {
26793
+ delete this.originalAxisLimits[chartId];
26794
+ delete this.currentAxesLimits[chartId];
26795
+ }
26796
+ }
26797
+ break;
26798
+ case "UPDATE_CHART":
26799
+ const type = cmd.definition.type;
26800
+ const chartId = `${type}-${cmd.figureId}`;
26801
+ if (!this.idConversion[cmd.figureId]) {
26802
+ this.idConversion[cmd.figureId] = new Set();
26803
+ }
26804
+ this.idConversion[cmd.figureId].add(chartId);
26805
+ if (!("zoomable" in cmd.definition && cmd.definition.zoomable)) {
26806
+ this.clearAxisLimits(chartId);
26807
+ }
26808
+ break;
26809
+ }
26810
+ }
26811
+ clearAxisLimits(chartId) {
26812
+ delete this.originalAxisLimits[chartId];
26813
+ delete this.currentAxesLimits[chartId];
26814
+ return "noStateChange";
26815
+ }
26816
+ resetAxisLimits(chartId, limits) {
26817
+ for (const axisId of ZOOMABLE_AXIS_IDS) {
26818
+ if (limits?.[axisId]) {
26819
+ if (!this.originalAxisLimits[chartId]?.[axisId]) {
26820
+ this.originalAxisLimits[chartId] = {
26821
+ ...this.originalAxisLimits[chartId],
26822
+ [axisId]: {},
26823
+ };
26824
+ }
26825
+ this.originalAxisLimits[chartId][axisId]["min"] = limits[axisId].min;
26826
+ this.originalAxisLimits[chartId][axisId]["max"] = limits[axisId].max;
26827
+ }
26828
+ else {
26829
+ if (this.originalAxisLimits[chartId]?.[axisId]) {
26830
+ delete this.originalAxisLimits[chartId][axisId];
26831
+ }
26832
+ }
26833
+ }
26834
+ return "noStateChange";
26835
+ }
26836
+ updateAxisLimits(chartId, limits) {
26837
+ if (limits === undefined) {
26838
+ delete this.currentAxesLimits[chartId];
26839
+ return "noStateChange";
26840
+ }
26841
+ let { min, max } = limits;
26842
+ if (min > max) {
26843
+ [min, max] = [max, min];
26844
+ }
26845
+ this.currentAxesLimits[chartId] = { x: { min, max } };
26846
+ return "noStateChange";
26847
+ }
26848
+ /* Update the trend line axis configuration based on the current axis limits.
26849
+ * This function calculates the new limits for the trend line axes based on the current x-axis
26850
+ * limits and the original limits of the trend line axes.
26851
+ * It assumes that the origininal trend line axes are linear transformations of the original x-axis
26852
+ * limits and applies the same transformation to the current x-axis limits to get the new limits
26853
+ * for the current trend line axes.
26854
+ */
26855
+ updateTrendLineConfiguration(chartId) {
26856
+ if (!this.originalAxisLimits[chartId]) {
26857
+ return "noStateChange";
26858
+ }
26859
+ const chartLimits = this.originalAxisLimits[chartId].x;
26860
+ if (chartLimits === undefined) {
26861
+ return "noStateChange";
26862
+ }
26863
+ for (const axisId of TREND_LINE_AXES_IDS) {
26864
+ if (!this.originalAxisLimits[chartId][axisId]) {
26865
+ continue;
26866
+ }
26867
+ if (!this.currentAxesLimits[chartId]?.[axisId]) {
26868
+ this.currentAxesLimits[chartId] = {
26869
+ ...this.currentAxesLimits[chartId],
26870
+ [axisId]: {},
26871
+ };
26872
+ }
26873
+ if (this.currentAxesLimits[chartId]?.x === undefined) {
26874
+ return "noStateChange";
26875
+ }
26876
+ const realRange = chartLimits.max - chartLimits.min;
26877
+ const trendingLimits = this.originalAxisLimits[chartId][axisId];
26878
+ const trendingRange = trendingLimits.max - trendingLimits.min;
26879
+ const slope = trendingRange / realRange;
26880
+ const intercept = trendingLimits.min - chartLimits.min * slope;
26881
+ const newXMin = this.currentAxesLimits[chartId].x.min;
26882
+ const newXMax = this.currentAxesLimits[chartId].x.max;
26883
+ this.currentAxesLimits[chartId][axisId].min = newXMin * slope + intercept;
26884
+ this.currentAxesLimits[chartId][axisId].max = newXMax * slope + intercept;
26885
+ }
26886
+ return "noStateChange";
26887
+ }
26888
+ }
26889
+
26890
+ const zoomWindowPlugin = {
26891
+ id: "zoomWindowPlugin",
26892
+ afterDatasetsDraw: function (chart, args, options) {
26893
+ if (!options.getLowerBound || !options.getUpperBound) {
26894
+ return;
26895
+ }
26896
+ const { ctx, chartArea: { left, right, top, bottom }, } = chart;
26897
+ let lowerBound = options.getLowerBound() ?? left;
26898
+ let upperBound = options.getUpperBound() ?? right;
26899
+ if (lowerBound > upperBound) {
26900
+ [lowerBound, upperBound] = [upperBound, lowerBound];
26901
+ }
26902
+ lowerBound = Math.max(left, lowerBound);
26903
+ upperBound = Math.min(right, upperBound);
26904
+ if (lowerBound === left) {
26905
+ lowerBound -= 1;
26906
+ }
26907
+ if (upperBound === right) {
26908
+ upperBound += 1;
26909
+ }
26910
+ ctx.save();
26911
+ ctx.fillStyle = "rgba(255,255,255,0.5)";
26912
+ ctx.beginPath();
26913
+ ctx.rect(left, bottom, lowerBound - left, top - bottom);
26914
+ ctx.rect(upperBound, bottom, right - upperBound, top - bottom);
26915
+ ctx.fill();
26916
+ ctx.beginPath();
26917
+ ctx.strokeStyle = "#bbb";
26918
+ ctx.rect(lowerBound, bottom, upperBound - lowerBound, top - bottom);
26919
+ ctx.stroke();
26920
+ ctx.lineWidth = 2;
26921
+ ctx.beginPath();
26922
+ ctx.moveTo(lowerBound - 3, (top + bottom) / 2 - 7);
26923
+ ctx.lineTo(lowerBound - 3, (top + bottom) / 2 + 7);
26924
+ ctx.stroke();
26925
+ ctx.beginPath();
26926
+ ctx.moveTo(upperBound + 3, (top + bottom) / 2 - 7);
26927
+ ctx.lineTo(upperBound + 3, (top + bottom) / 2 + 7);
26928
+ ctx.stroke();
26929
+ ctx.restore();
26930
+ },
26931
+ };
26932
+
26933
+ css /* scss */ `
26934
+ .o-spreadsheet {
26935
+ .o-master-chart-container {
26936
+ height: ${MASTER_CHART_HEIGHT}px;
26937
+ }
26938
+ }
26939
+ `;
26940
+ chartJsExtensionRegistry.add("zoomWindowPlugin", {
26941
+ register: (Chart) => Chart.register(zoomWindowPlugin),
26942
+ unregister: (Chart) => Chart.unregister(zoomWindowPlugin),
26943
+ });
26944
+ class ZoomableChartJsComponent extends ChartJsComponent {
26945
+ static template = "o-spreadsheet-ZoomableChartJsComponent";
26946
+ store;
26947
+ fullScreenChartStore;
26948
+ masterChartCanvas = owl.useRef("masterChartCanvas");
26949
+ masterChart;
26950
+ mode;
26951
+ hasLinearScale;
26952
+ isBarChart;
26953
+ chartId = "";
26954
+ datasetBoundaries = { xMin: 0, xMax: 0 };
26955
+ removeEventListeners = () => { };
26956
+ setup() {
26957
+ this.store = useStore(ZoomableChartStore);
26958
+ this.fullScreenChartStore = useStore(FullScreenChartStore);
26959
+ super.setup();
26960
+ }
26961
+ unmount() {
26962
+ super.unmount();
26963
+ this.masterChart?.destroy();
26964
+ this.removeEventListeners();
26965
+ }
26966
+ get containerStyle() {
26967
+ const height = this.sliceable ? `calc(100% - ${MASTER_CHART_HEIGHT}px)` : "100%";
26968
+ return `
26969
+ height:${height};
26970
+ `;
26971
+ }
26972
+ get sliceable() {
26973
+ if (this.env.isDashboard()) {
26974
+ const fullScreenFigureId = this.fullScreenChartStore.fullScreenFigure?.id;
26975
+ const chartFigureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
26976
+ if (fullScreenFigureId === chartFigureId) {
26977
+ return true;
26978
+ }
26979
+ }
26980
+ const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
26981
+ return ("zoomable" in definition && definition?.zoomable) ?? false;
26982
+ }
26983
+ get axisOffset() {
26984
+ return !this.hasLinearScale && this.isBarChart ? 0.5 : 0;
26985
+ }
26986
+ getMasterChartConfiguration(chartData) {
26987
+ const config = chartData;
26988
+ return {
26989
+ ...config,
26990
+ options: {
26991
+ ...config.options,
26992
+ plugins: {
26993
+ ...config.options.plugins,
26994
+ zoomWindowPlugin: {
26995
+ getLowerBound: () => this.lowerBound,
26996
+ getUpperBound: () => this.upperBound,
26997
+ },
26998
+ },
26999
+ },
27000
+ };
27001
+ }
27002
+ getDetailChartConfiguration(chartData) {
27003
+ if (!this.sliceable) {
27004
+ return chartData;
27005
+ }
27006
+ const xAxis = this.store.currentAxesLimits[this.chartId]?.x;
27007
+ const xScale = {
27008
+ ...chartData.options.scales?.x,
27009
+ };
27010
+ if (xAxis?.min !== undefined) {
27011
+ xScale.min = this.hasLinearScale ? xAxis.min : Math.ceil(xAxis.min) - this.axisOffset;
27012
+ }
27013
+ if (xAxis?.max !== undefined) {
27014
+ xScale.max = this.hasLinearScale ? xAxis.max : Math.floor(xAxis.max) - this.axisOffset;
27015
+ }
27016
+ return {
27017
+ ...chartData,
27018
+ options: {
27019
+ ...chartData.options,
27020
+ scales: {
27021
+ ...chartData.options.scales,
27022
+ x: xScale,
27023
+ },
27024
+ layout: {
27025
+ ...chartData.options.layout,
27026
+ padding: {
27027
+ ...chartData.options.layout?.padding,
27028
+ bottom: 5,
27029
+ },
27030
+ },
27031
+ },
27032
+ };
27033
+ }
27034
+ getAxisLimitsFromDataset(chartData) {
27035
+ const data = chartData.data.datasets.map((ds) => ds.data).flat();
27036
+ const xValues = data.map((d, i) => (typeof d === "object" && d !== null ? d.x : i));
27037
+ const xMin = Math.min(...xValues);
27038
+ const xMax = Math.max(...xValues);
27039
+ return { xMin, xMax };
27040
+ }
27041
+ get shouldAnimate() {
27042
+ return this.env.model.getters.isDashboard() && !this.sliceable;
27043
+ }
27044
+ createChart(chartRuntime) {
27045
+ const chartData = chartRuntime.chartJsConfig;
27046
+ this.isBarChart = chartData.type === "bar";
27047
+ this.chartId = `${chartData.type}-${this.props.chartId}`;
27048
+ this.datasetBoundaries = this.getAxisLimitsFromDataset(chartData);
27049
+ if (this.sliceable) {
27050
+ const updatedData = this.getDetailChartConfiguration(chartData);
27051
+ chartRuntime.chartJsConfig = updatedData;
27052
+ }
27053
+ super.createChart(chartRuntime);
27054
+ this.hasLinearScale = this.chart?.scales?.x.type === "linear";
27055
+ if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
27056
+ return;
27057
+ }
27058
+ this.masterChart?.destroy();
27059
+ const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27060
+ this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
27061
+ this.resetAxesLimits();
27062
+ }
27063
+ updateChartJs(chartRuntime) {
27064
+ const chartData = chartRuntime.chartJsConfig;
27065
+ const newDatasetBoundaries = this.getAxisLimitsFromDataset(chartData);
27066
+ if (this.datasetBoundaries.xMin !== newDatasetBoundaries.xMin ||
27067
+ this.datasetBoundaries.xMax !== newDatasetBoundaries.xMax) {
27068
+ this.store.clearAxisLimits(this.chartId);
27069
+ this.datasetBoundaries = newDatasetBoundaries;
27070
+ }
27071
+ this.isBarChart = chartData?.type === "bar";
27072
+ this.chartId = `${chartData.type}-${this.props.chartId}`;
27073
+ if (this.sliceable) {
27074
+ const updatedData = this.getDetailChartConfiguration(chartData);
27075
+ chartRuntime.chartJsConfig = updatedData;
27076
+ }
27077
+ super.updateChartJs(chartRuntime);
27078
+ this.hasLinearScale = this.chart?.scales?.x.type === "linear";
27079
+ if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
27080
+ this.masterChart = undefined;
27081
+ }
27082
+ else {
27083
+ const masterChartConfig = this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]);
27084
+ if (!this.masterChart) {
27085
+ const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27086
+ this.masterChart = new window.Chart(masterChartCtx, masterChartConfig);
27087
+ }
27088
+ else {
27089
+ this.masterChart.data = masterChartConfig.data;
27090
+ this.masterChart.config.options = masterChartConfig.options;
27091
+ this.masterChart.update();
27092
+ }
27093
+ }
27094
+ this.resetAxesLimits();
27095
+ }
27096
+ resetAxesLimits() {
27097
+ if (!this.chart) {
27098
+ return;
27099
+ }
27100
+ const previousAxisLimits = this.store.originalAxisLimits[this.chartId];
27101
+ if (previousAxisLimits?.x?.min === undefined && previousAxisLimits?.x?.max === undefined) {
27102
+ let scales = this.masterChart
27103
+ ? this.masterChart.scales
27104
+ : this.chart.scales;
27105
+ if (!this.hasLinearScale && scales?.x) {
27106
+ scales = {
27107
+ ...scales,
27108
+ x: {
27109
+ min: Math.ceil(scales.x.min) - this.axisOffset,
27110
+ max: Math.floor(scales.x.max) + this.axisOffset,
27111
+ },
27112
+ };
27113
+ }
27114
+ this.store.resetAxisLimits(this.chartId, scales);
27115
+ return;
27116
+ }
27117
+ this.updateTrendingLineAxes();
27118
+ this.chart.update();
27119
+ if (!this.masterChart) {
27120
+ return;
27121
+ }
27122
+ this.masterChart.update();
27123
+ }
27124
+ updateTrendingLineAxes() {
27125
+ this.store.updateTrendLineConfiguration(this.chartId);
27126
+ const config = this.store.currentAxesLimits[this.chartId];
27127
+ for (const axisId of [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID]) {
27128
+ if (!this.chart?.config.options?.scales?.[axisId] || !config?.[axisId]) {
27129
+ continue;
27130
+ }
27131
+ this.chart.config.options.scales[axisId].min = config[axisId].min;
27132
+ this.chart.config.options.scales[axisId].max = config[axisId].max;
27133
+ }
27134
+ }
27135
+ get upperBound() {
27136
+ return this.computePosition(this.store.currentAxesLimits[this.chartId]?.x?.max);
27137
+ }
27138
+ get lowerBound() {
27139
+ return this.computePosition(this.store.currentAxesLimits[this.chartId]?.x?.min);
27140
+ }
27141
+ computePosition(value) {
27142
+ if (value === undefined || !this.masterChart?.scales?.x) {
27143
+ return undefined;
27144
+ }
27145
+ const scale = this.masterChart.scales.x;
27146
+ if (this.hasLinearScale) {
27147
+ return scale.getPixelForValue(value);
27148
+ }
27149
+ if (!this.masterChart.chartArea) {
27150
+ return undefined;
27151
+ }
27152
+ const { left, right } = this.masterChart.chartArea;
27153
+ const { min, max } = scale;
27154
+ const offset = this.axisOffset;
27155
+ return left + ((right - left) * (offset + value - min)) / (2 * offset + max - min);
27156
+ }
27157
+ computeCoordinate(position) {
27158
+ if (!this.masterChart) {
27159
+ return undefined;
27160
+ }
27161
+ const scale = this.masterChart.scales.x;
27162
+ if (this.hasLinearScale) {
27163
+ const value = scale.getValueForPixel(position);
27164
+ if (value === undefined) {
27165
+ return undefined;
27166
+ }
27167
+ return Math.round(value * 100) / 100;
27168
+ }
27169
+ const { left, right } = this.masterChart.chartArea;
27170
+ const offset = this.axisOffset;
27171
+ return (scale.min -
27172
+ offset +
27173
+ ((scale.max + 2 * offset - scale.min) * (position - left)) / (right - left));
27174
+ }
27175
+ updateAxisLimits(xMin, xMax) {
27176
+ if (!this.hasLinearScale) {
27177
+ this.chart.config.options.scales.x.min = Math.ceil(xMin);
27178
+ this.chart.config.options.scales.x.max = Math.floor(xMax);
27179
+ }
27180
+ else {
27181
+ this.chart.config.options.scales.x.min = xMin;
27182
+ this.chart.config.options.scales.x.max = xMax;
27183
+ }
27184
+ this.store.updateAxisLimits(this.chartId, { min: xMin, max: xMax });
27185
+ this.updateTrendingLineAxes();
27186
+ this.masterChart?.update();
27187
+ this.chart?.update();
27188
+ }
27189
+ onPointerDownInMasterChart(ev) {
27190
+ this.removeEventListeners();
27191
+ const position = ev.offsetX;
27192
+ if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27193
+ return;
27194
+ }
27195
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27196
+ const xMax = this.upperBound ?? right;
27197
+ const xMin = this.lowerBound ?? left;
27198
+ if (position < left - 5 || position > right + 5 || ev.offsetY < top || ev.offsetY > bottom) {
27199
+ return;
27200
+ }
27201
+ ev.preventDefault();
27202
+ ev.stopPropagation();
27203
+ let startingPositionOnChart, windowSize, startX;
27204
+ const startingEventPosition = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27205
+ if ((xMin !== left || xMax !== right) && position > xMin + 5 && position < xMax - 5) {
27206
+ startingPositionOnChart = ev.offsetX - xMin;
27207
+ this.mode = "moveInMaster";
27208
+ const currentLimits = this.store.currentAxesLimits[this.chartId]?.x;
27209
+ windowSize =
27210
+ (currentLimits?.max ?? this.chart.scales.x.max) -
27211
+ (currentLimits?.min ?? this.chart.scales.x.min);
27212
+ }
27213
+ else {
27214
+ this.mode = "selectInMaster";
27215
+ if (Math.abs(position - xMin) < 5) {
27216
+ startingPositionOnChart = xMax;
27217
+ }
27218
+ else if (Math.abs(position - xMax) < 5) {
27219
+ startingPositionOnChart = xMin;
27220
+ }
27221
+ else {
27222
+ startingPositionOnChart = clip(position, left, right);
27223
+ }
27224
+ startX = this.computeCoordinate(startingPositionOnChart);
27225
+ }
27226
+ const originalXMin = this.store.originalAxisLimits[this.chartId].x.min;
27227
+ const originalXMax = this.store.originalAxisLimits[this.chartId].x.max;
27228
+ const computeNewAxisLimits = (position) => {
27229
+ let xMin, xMax;
27230
+ const { left, right } = this.masterChart.chartArea;
27231
+ if (this.mode === "moveInMaster") {
27232
+ xMin = this.computeCoordinate(position - startingPositionOnChart);
27233
+ if (xMin < originalXMin) {
27234
+ xMin = originalXMin;
27235
+ }
27236
+ else if (xMin > originalXMax - windowSize) {
27237
+ xMin = originalXMax - windowSize;
27238
+ }
27239
+ xMax = xMin + windowSize;
27240
+ }
27241
+ else if (this.mode === "selectInMaster") {
27242
+ const upperBound = clip(position, left, right);
27243
+ if (Math.abs(startingPositionOnChart - upperBound) > 5) {
27244
+ const endX = this.computeCoordinate(upperBound);
27245
+ if (startX === undefined || endX === undefined) {
27246
+ return {};
27247
+ }
27248
+ xMin = Math.min(startX, endX);
27249
+ xMax = Math.max(startX, endX);
27250
+ }
27251
+ }
27252
+ return { min: xMin, max: xMax };
27253
+ };
27254
+ const onDragFromMasterChart = (ev) => {
27255
+ const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27256
+ if (Math.abs(position - startingEventPosition) < 5) {
27257
+ return;
27258
+ }
27259
+ const { min: xMin, max: xMax } = computeNewAxisLimits(position);
27260
+ if (xMin !== undefined && xMax !== undefined) {
27261
+ this.updateAxisLimits(xMin, xMax);
27262
+ }
27263
+ };
27264
+ const onPointerUpInMasterChart = (ev) => {
27265
+ this.removeEventListeners();
27266
+ const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
27267
+ if (Math.abs(position - startingEventPosition) > 5) {
27268
+ let { min: xMin, max: xMax } = computeNewAxisLimits(position);
27269
+ if (xMin !== undefined && xMax !== undefined) {
27270
+ if (!this.hasLinearScale) {
27271
+ if (this.mode === "moveInMaster" && windowSize && !this.isBarChart) {
27272
+ xMin = Math.round(xMin) - this.axisOffset;
27273
+ xMax = xMin + windowSize;
27274
+ }
27275
+ else {
27276
+ xMin = Math.ceil(xMin) - this.axisOffset;
27277
+ xMax = Math.floor(xMax) + this.axisOffset;
27278
+ }
27279
+ }
27280
+ this.updateAxisLimits(xMin, xMax);
27281
+ }
27282
+ }
27283
+ this.mode = undefined;
27284
+ };
27285
+ this.removeEventListeners = () => {
27286
+ window.removeEventListener("pointermove", onDragFromMasterChart, true);
27287
+ window.removeEventListener("pointerup", onPointerUpInMasterChart, true);
27288
+ };
27289
+ window.addEventListener("pointermove", onDragFromMasterChart, true);
27290
+ window.addEventListener("pointerup", onPointerUpInMasterChart, true);
27291
+ }
27292
+ onPointerMoveInMasterChart(ev) {
27293
+ const { offsetX: x, offsetY: y } = ev;
27294
+ if (this.mode === undefined) {
27295
+ const target = ev.target;
27296
+ if (!this.masterChart?.chartArea) {
27297
+ target["style"].cursor = "default";
27298
+ return;
27299
+ }
27300
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27301
+ const start = this.lowerBound ?? left;
27302
+ const end = this.upperBound ?? right;
27303
+ if (y < top || y > bottom) {
27304
+ target["style"].cursor = "default";
27305
+ }
27306
+ else if (Math.abs(start - x) < 5 || Math.abs(end - x) < 5) {
27307
+ target["style"].cursor = "ew-resize";
27308
+ }
27309
+ else if (start < x && x < end && (start !== left || end !== right)) {
27310
+ target["style"].cursor = "grab";
27311
+ }
27312
+ else {
27313
+ target["style"].cursor = "crosshair";
27314
+ }
27315
+ }
27316
+ }
27317
+ onMouseLeaveMasterChart(ev) {
27318
+ const target = ev.target;
27319
+ if (!target) {
27320
+ return;
27321
+ }
27322
+ target["style"].cursor = "default";
27323
+ }
27324
+ onDoubleClickInMasterChart(ev) {
27325
+ this.mode = undefined;
27326
+ const position = ev.offsetX;
27327
+ if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27328
+ return;
27329
+ }
27330
+ const { left, right, top, bottom } = this.masterChart.chartArea;
27331
+ let upperBound = this.upperBound ?? right;
27332
+ let lowerBound = this.lowerBound ?? left;
27333
+ if (upperBound < lowerBound) {
27334
+ [upperBound, lowerBound] = [lowerBound, upperBound];
27335
+ }
27336
+ if (position < left - 5 || position > right + 5 || ev.offsetY < top || ev.offsetY > bottom) {
27337
+ return;
27338
+ }
27339
+ ev.preventDefault();
27340
+ ev.stopPropagation();
27341
+ let { min: xMin, max: xMax } = this.store.currentAxesLimits[this.chartId]?.x ?? this.chart.scales.x;
27342
+ const originalAxisLimits = this.store.originalAxisLimits[this.chartId].x;
27343
+ if (!originalAxisLimits) {
27344
+ return;
27345
+ }
27346
+ let originalXMin = originalAxisLimits.min;
27347
+ let originalXMax = originalAxisLimits.max;
27348
+ if (this.hasLinearScale) {
27349
+ originalXMin = Math.ceil(originalXMin) - this.axisOffset;
27350
+ originalXMax = Math.floor(originalXMax) + this.axisOffset;
27351
+ }
27352
+ if (Math.abs(position - lowerBound) < 5) {
27353
+ // Reset to original min
27354
+ xMin = originalXMin;
27355
+ }
27356
+ else if (Math.abs(position - upperBound) < 5) {
27357
+ xMax = originalXMax;
27358
+ }
27359
+ else if (lowerBound < position && position < upperBound) {
27360
+ // Reset to original limits
27361
+ xMin = originalXMin;
27362
+ xMax = originalXMax;
27363
+ }
27364
+ else {
27365
+ return;
27366
+ }
27367
+ this.updateAxisLimits(xMin, xMax);
27368
+ }
27369
+ }
27370
+
26732
27371
  const cellAnimationRegistry = new Registry();
26733
27372
  cellAnimationRegistry.add("animatedBackgroundColorChange", {
26734
27373
  id: "animatedBackgroundColorChange",
@@ -27202,6 +27841,7 @@ class ComboChart extends AbstractChart {
27202
27841
  type = "combo";
27203
27842
  showValues;
27204
27843
  hideDataMarkers;
27844
+ zoomable;
27205
27845
  constructor(definition, sheetId, getters) {
27206
27846
  super(definition, sheetId, getters);
27207
27847
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -27214,6 +27854,7 @@ class ComboChart extends AbstractChart {
27214
27854
  this.axesDesign = definition.axesDesign;
27215
27855
  this.showValues = definition.showValues;
27216
27856
  this.hideDataMarkers = definition.hideDataMarkers;
27857
+ this.zoomable = definition.zoomable;
27217
27858
  }
27218
27859
  static transformDefinition(chartSheetId, definition, applyChange) {
27219
27860
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -27263,6 +27904,7 @@ class ComboChart extends AbstractChart {
27263
27904
  axesDesign: this.axesDesign,
27264
27905
  showValues: this.showValues,
27265
27906
  hideDataMarkers: this.hideDataMarkers,
27907
+ zoomable: this.zoomable,
27266
27908
  };
27267
27909
  }
27268
27910
  getDefinitionForExcel() {
@@ -27306,6 +27948,7 @@ class ComboChart extends AbstractChart {
27306
27948
  axesDesign: context.axesDesign,
27307
27949
  showValues: context.showValues,
27308
27950
  hideDataMarkers: context.hideDataMarkers,
27951
+ zoomable: context.zoomable,
27309
27952
  };
27310
27953
  }
27311
27954
  duplicateInDuplicatedSheet(newSheetId) {
@@ -27911,6 +28554,7 @@ class LineChart extends AbstractChart {
27911
28554
  fillArea;
27912
28555
  showValues;
27913
28556
  hideDataMarkers;
28557
+ zoomable;
27914
28558
  constructor(definition, sheetId, getters) {
27915
28559
  super(definition, sheetId, getters);
27916
28560
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -27927,6 +28571,7 @@ class LineChart extends AbstractChart {
27927
28571
  this.fillArea = definition.fillArea;
27928
28572
  this.showValues = definition.showValues;
27929
28573
  this.hideDataMarkers = definition.hideDataMarkers;
28574
+ this.zoomable = definition.zoomable;
27930
28575
  }
27931
28576
  static validateChartDefinition(validator, definition) {
27932
28577
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -27951,6 +28596,7 @@ class LineChart extends AbstractChart {
27951
28596
  fillArea: context.fillArea,
27952
28597
  showValues: context.showValues,
27953
28598
  hideDataMarkers: context.hideDataMarkers,
28599
+ zoomable: context.zoomable,
27954
28600
  };
27955
28601
  }
27956
28602
  getDefinition() {
@@ -27982,6 +28628,7 @@ class LineChart extends AbstractChart {
27982
28628
  fillArea: this.fillArea,
27983
28629
  showValues: this.showValues,
27984
28630
  hideDataMarkers: this.hideDataMarkers,
28631
+ zoomable: this.zoomable,
27985
28632
  };
27986
28633
  }
27987
28634
  getContextCreation() {
@@ -28504,6 +29151,7 @@ class ScatterChart extends AbstractChart {
28504
29151
  dataSetDesign;
28505
29152
  axesDesign;
28506
29153
  showValues;
29154
+ zoomable;
28507
29155
  constructor(definition, sheetId, getters) {
28508
29156
  super(definition, sheetId, getters);
28509
29157
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -28516,6 +29164,7 @@ class ScatterChart extends AbstractChart {
28516
29164
  this.dataSetDesign = definition.dataSets;
28517
29165
  this.axesDesign = definition.axesDesign;
28518
29166
  this.showValues = definition.showValues;
29167
+ this.zoomable = definition.zoomable;
28519
29168
  }
28520
29169
  static validateChartDefinition(validator, definition) {
28521
29170
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -28536,6 +29185,7 @@ class ScatterChart extends AbstractChart {
28536
29185
  aggregated: context.aggregated ?? false,
28537
29186
  axesDesign: context.axesDesign,
28538
29187
  showValues: context.showValues,
29188
+ zoomable: context.zoomable,
28539
29189
  };
28540
29190
  }
28541
29191
  getDefinition() {
@@ -28563,6 +29213,7 @@ class ScatterChart extends AbstractChart {
28563
29213
  aggregated: this.aggregated,
28564
29214
  axesDesign: this.axesDesign,
28565
29215
  showValues: this.showValues,
29216
+ zoomable: this.zoomable,
28566
29217
  };
28567
29218
  }
28568
29219
  getContextCreation() {
@@ -28947,6 +29598,7 @@ class WaterfallChart extends AbstractChart {
28947
29598
  dataSetDesign;
28948
29599
  axesDesign;
28949
29600
  showValues;
29601
+ zoomable;
28950
29602
  constructor(definition, sheetId, getters) {
28951
29603
  super(definition, sheetId, getters);
28952
29604
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -28965,6 +29617,7 @@ class WaterfallChart extends AbstractChart {
28965
29617
  this.dataSetDesign = definition.dataSets;
28966
29618
  this.axesDesign = definition.axesDesign;
28967
29619
  this.showValues = definition.showValues;
29620
+ this.zoomable = definition.zoomable;
28968
29621
  }
28969
29622
  static transformDefinition(chartSheetId, definition, applyChange) {
28970
29623
  return transformChartDefinitionWithDataSetsWithZone(chartSheetId, definition, applyChange);
@@ -28988,6 +29641,7 @@ class WaterfallChart extends AbstractChart {
28988
29641
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
28989
29642
  axesDesign: context.axesDesign,
28990
29643
  showValues: context.showValues,
29644
+ zoomable: context.zoomable ?? false,
28991
29645
  };
28992
29646
  }
28993
29647
  getContextCreation() {
@@ -29047,6 +29701,7 @@ class WaterfallChart extends AbstractChart {
29047
29701
  firstValueAsSubtotal: this.firstValueAsSubtotal,
29048
29702
  axesDesign: this.axesDesign,
29049
29703
  showValues: this.showValues,
29704
+ zoomable: this.zoomable,
29050
29705
  };
29051
29706
  }
29052
29707
  getDefinitionForExcel() {
@@ -29223,14 +29878,14 @@ chartRegistry.add("treemap", {
29223
29878
  sequence: 100,
29224
29879
  });
29225
29880
  const chartComponentRegistry = new Registry();
29226
- chartComponentRegistry.add("line", ChartJsComponent);
29227
- chartComponentRegistry.add("bar", ChartJsComponent);
29228
- chartComponentRegistry.add("combo", ChartJsComponent);
29881
+ chartComponentRegistry.add("line", ZoomableChartJsComponent);
29882
+ chartComponentRegistry.add("bar", ZoomableChartJsComponent);
29883
+ chartComponentRegistry.add("combo", ZoomableChartJsComponent);
29229
29884
  chartComponentRegistry.add("pie", ChartJsComponent);
29230
29885
  chartComponentRegistry.add("gauge", GaugeChartComponent);
29231
- chartComponentRegistry.add("scatter", ChartJsComponent);
29886
+ chartComponentRegistry.add("scatter", ZoomableChartJsComponent);
29232
29887
  chartComponentRegistry.add("scorecard", ScorecardChart);
29233
- chartComponentRegistry.add("waterfall", ChartJsComponent);
29888
+ chartComponentRegistry.add("waterfall", ZoomableChartJsComponent);
29234
29889
  chartComponentRegistry.add("pyramid", ChartJsComponent);
29235
29890
  chartComponentRegistry.add("radar", ChartJsComponent);
29236
29891
  chartComponentRegistry.add("geo", ChartJsComponent);
@@ -29429,6 +30084,64 @@ chartSubtypeRegistry
29429
30084
  preview: "o-spreadsheet-ChartPreview.TREE_MAP_CHART",
29430
30085
  });
29431
30086
 
30087
+ function generateMasterChartConfig(chartJsConfig) {
30088
+ return {
30089
+ ...chartJsConfig,
30090
+ data: {
30091
+ ...chartJsConfig.data,
30092
+ datasets: chartJsConfig.data.datasets
30093
+ .filter((ds) => !isTrendLineAxis(ds["xAxisID"]))
30094
+ .map((ds) => ({
30095
+ ...ds,
30096
+ pointRadius: 0,
30097
+ showLine: true,
30098
+ })),
30099
+ },
30100
+ options: {
30101
+ ...chartJsConfig.options,
30102
+ hover: { mode: null },
30103
+ plugins: {
30104
+ ...chartJsConfig.options.plugins,
30105
+ title: { display: false },
30106
+ legend: { display: false },
30107
+ tooltip: { enabled: false },
30108
+ chartShowValuesPlugin: undefined,
30109
+ },
30110
+ layout: {
30111
+ padding: {
30112
+ ...chartJsConfig.options.layout?.padding,
30113
+ top: 5,
30114
+ bottom: 10,
30115
+ },
30116
+ },
30117
+ scales: {
30118
+ y: {
30119
+ ...chartJsConfig.options.scales?.y,
30120
+ display: false,
30121
+ },
30122
+ y1: {
30123
+ ...chartJsConfig.options.scales?.y1,
30124
+ display: false,
30125
+ },
30126
+ x: {
30127
+ ...chartJsConfig.options.scales?.x,
30128
+ title: undefined,
30129
+ ticks: {
30130
+ ...chartJsConfig.options.scales?.x?.ticks,
30131
+ callback: function (value) {
30132
+ return truncateLabel(chartJsConfig.options.scales?.x?.ticks?.callback?.call(this, value), 5);
30133
+ },
30134
+ padding: 0,
30135
+ font: {
30136
+ size: 9,
30137
+ },
30138
+ },
30139
+ },
30140
+ },
30141
+ },
30142
+ };
30143
+ }
30144
+
29432
30145
  /**
29433
30146
  * Create a function used to create a Chart based on the definition
29434
30147
  */
@@ -29454,7 +30167,13 @@ function chartRuntimeFactory(getters) {
29454
30167
  if (!builder) {
29455
30168
  throw new Error("No runtime builder for this chart.");
29456
30169
  }
29457
- return builder.getChartRuntime(chart, getters);
30170
+ const runtime = builder.getChartRuntime(chart, getters);
30171
+ const definition = chart.getDefinition();
30172
+ if ("chartJsConfig" in runtime && /line|combo|bar|scatter|waterfall/.test(definition.type)) {
30173
+ const chartJsConfig = runtime.chartJsConfig;
30174
+ runtime["masterChartConfig"] = generateMasterChartConfig(chartJsConfig);
30175
+ }
30176
+ return runtime;
29458
30177
  }
29459
30178
  return createRuntimeChart;
29460
30179
  }
@@ -30145,26 +30864,6 @@ class CarouselFigure extends owl.Component {
30145
30864
  }
30146
30865
  }
30147
30866
 
30148
- class FullScreenChartStore extends SpreadsheetStore {
30149
- mutators = ["toggleFullScreenChart"];
30150
- fullScreenFigure = undefined;
30151
- toggleFullScreenChart(figureId) {
30152
- if (this.fullScreenFigure?.id === figureId) {
30153
- this.fullScreenFigure = undefined;
30154
- }
30155
- else {
30156
- this.makeFullScreen(figureId);
30157
- }
30158
- }
30159
- makeFullScreen(figureId) {
30160
- const sheetId = this.getters.getActiveSheetId();
30161
- const figure = this.getters.getFigure(sheetId, figureId);
30162
- if (figure) {
30163
- this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
30164
- }
30165
- }
30166
- }
30167
-
30168
30867
  /**
30169
30868
  * Repeatedly calls a callback function with a time delay between calls.
30170
30869
  */
@@ -31027,16 +31726,6 @@ class ChartDashboardMenu extends owl.Component {
31027
31726
  }
31028
31727
  }
31029
31728
 
31030
- // -----------------------------------------------------------------------------
31031
- // STYLE
31032
- // -----------------------------------------------------------------------------
31033
- css /* scss */ `
31034
- .o-chart-container {
31035
- width: 100%;
31036
- height: 100%;
31037
- position: relative;
31038
- }
31039
- `;
31040
31729
  class ChartFigure extends owl.Component {
31041
31730
  static template = "o-spreadsheet-ChartFigure";
31042
31731
  static props = {
@@ -42952,10 +43641,18 @@ const REINSERT_STATIC_PIVOT_CHILDREN = (env) => env.model.getters.getPivotIds().
42952
43641
  sequence: index,
42953
43642
  execute: (env) => {
42954
43643
  const zone = env.model.getters.getSelectedZone();
42955
- const table = env.model.getters.getPivot(pivotId).getExpandedTableStructure().export();
43644
+ const table = env.model.getters.getPivot(pivotId).getExpandedTableStructure();
43645
+ if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
43646
+ env.notifyUser({
43647
+ type: "warning",
43648
+ text: getPivotTooBigErrorMessage(table.numberOfCells, env.model.getters.getLocale()),
43649
+ sticky: true,
43650
+ });
43651
+ return;
43652
+ }
42956
43653
  env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
42957
43654
  pivotId,
42958
- table,
43655
+ table: table.export(),
42959
43656
  col: zone.left,
42960
43657
  row: zone.top,
42961
43658
  sheetId: env.model.getters.getActiveSheetId(),
@@ -44293,7 +44990,7 @@ const insertDropdown = {
44293
44990
  env.openSidePanel("DataValidationEditor", {
44294
44991
  rule: localizeDataValidationRule(rule, env.model.getters.getLocale()),
44295
44992
  onExit: () => {
44296
- env.openSidePanel("DataValidation");
44993
+ env.replaceSidePanel("DataValidation", "DataValidationEditor");
44297
44994
  },
44298
44995
  });
44299
44996
  },
@@ -45029,6 +45726,9 @@ class SpreadsheetPivotTable {
45029
45726
  return [row, ...this.rowTreeToRows(node.children, row)];
45030
45727
  });
45031
45728
  }
45729
+ get numberOfCells() {
45730
+ return this.rows.length * this.getNumberOfDataColumns();
45731
+ }
45032
45732
  }
45033
45733
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
45034
45734
 
@@ -52846,10 +53546,23 @@ class ChartShowDataMarkers extends owl.Component {
52846
53546
  };
52847
53547
  }
52848
53548
 
52849
- class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
52850
- static template = "o-spreadsheet-ComboChartDesignPanel";
53549
+ class GenericZoomableChartDesignPanel extends ChartWithAxisDesignPanel {
53550
+ static template = "o-spreadsheet-GenericZoomableChartDesignPanel";
52851
53551
  static components = {
52852
53552
  ...ChartWithAxisDesignPanel.components,
53553
+ Checkbox,
53554
+ };
53555
+ onToggleZoom(zoomable) {
53556
+ this.props.updateChart(this.props.chartId, {
53557
+ zoomable,
53558
+ });
53559
+ }
53560
+ }
53561
+
53562
+ class ComboChartDesignPanel extends GenericZoomableChartDesignPanel {
53563
+ static template = "o-spreadsheet-ComboChartDesignPanel";
53564
+ static components = {
53565
+ ...GenericZoomableChartDesignPanel.components,
52853
53566
  ChartShowDataMarkers,
52854
53567
  RadioSelection,
52855
53568
  };
@@ -53293,10 +54006,10 @@ class LineConfigPanel extends GenericChartConfigPanel {
53293
54006
  }
53294
54007
  }
53295
54008
 
53296
- class LineChartDesignPanel extends ChartWithAxisDesignPanel {
54009
+ class LineChartDesignPanel extends GenericZoomableChartDesignPanel {
53297
54010
  static template = "o-spreadsheet-LineChartDesignPanel";
53298
54011
  static components = {
53299
- ...ChartWithAxisDesignPanel.components,
54012
+ ...GenericZoomableChartDesignPanel.components,
53300
54013
  ChartShowDataMarkers,
53301
54014
  };
53302
54015
  }
@@ -53751,6 +54464,11 @@ class WaterfallChartDesignPanel extends owl.Component {
53751
54464
  verticalAxisPosition: value,
53752
54465
  });
53753
54466
  }
54467
+ onToggleZoom(zoomable) {
54468
+ this.props.updateChart(this.props.chartId, {
54469
+ zoomable,
54470
+ });
54471
+ }
53754
54472
  }
53755
54473
 
53756
54474
  const chartSidePanelComponentRegistry = new Registry();
@@ -53761,11 +54479,11 @@ chartSidePanelComponentRegistry
53761
54479
  })
53762
54480
  .add("scatter", {
53763
54481
  configuration: ScatterConfigPanel,
53764
- design: ChartWithAxisDesignPanel,
54482
+ design: GenericZoomableChartDesignPanel,
53765
54483
  })
53766
54484
  .add("bar", {
53767
54485
  configuration: BarConfigPanel,
53768
- design: ChartWithAxisDesignPanel,
54486
+ design: GenericZoomableChartDesignPanel,
53769
54487
  })
53770
54488
  .add("combo", {
53771
54489
  configuration: GenericChartConfigPanel,
@@ -56001,11 +56719,15 @@ class PivotMeasureDisplayPanel extends owl.Component {
56001
56719
  this.store = useLocalStore(PivotMeasureDisplayPanelStore, this.props.pivotId, this.props.measure);
56002
56720
  }
56003
56721
  onSave() {
56004
- this.env.openSidePanel("PivotSidePanel", { pivotId: this.props.pivotId });
56722
+ this.env.replaceSidePanel("PivotSidePanel", `pivot_measure_display_${this.props.pivotId}_${this.props.measure.id}`, {
56723
+ pivotId: this.props.pivotId,
56724
+ });
56005
56725
  }
56006
56726
  onCancel() {
56007
56727
  this.store.cancelMeasureDisplayEdition();
56008
- this.env.openSidePanel("PivotSidePanel", { pivotId: this.props.pivotId });
56728
+ this.env.replaceSidePanel("PivotSidePanel", `pivot_measure_display_${this.props.pivotId}_${this.props.measure.id}`, {
56729
+ pivotId: this.props.pivotId,
56730
+ });
56009
56731
  }
56010
56732
  get fieldChoices() {
56011
56733
  return this.store.fields.map((field) => ({
@@ -56422,7 +57144,7 @@ class PivotMeasureEditor extends owl.Component {
56422
57144
  });
56423
57145
  }
56424
57146
  openShowValuesAs() {
56425
- this.env.openSidePanel("PivotMeasureDisplayPanel", {
57147
+ this.env.replaceSidePanel("PivotMeasureDisplayPanel", `pivot_key_${this.props.pivotId}`, {
56426
57148
  pivotId: this.props.pivotId,
56427
57149
  measure: this.props.measure,
56428
57150
  });
@@ -56683,7 +57405,7 @@ class PivotLayoutConfigurator extends owl.Component {
56683
57405
  this.props.onDimensionsUpdated(update);
56684
57406
  }
56685
57407
  getMeasureId(fieldName, aggregator) {
56686
- const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
57408
+ const baseId = fieldName.replaceAll("'", "") + (aggregator ? `:${aggregator}` : "");
56687
57409
  let id = baseId;
56688
57410
  let i = 2;
56689
57411
  while (this.props.definition.measures.some((m) => m.id === id)) {
@@ -56761,6 +57483,13 @@ class PivotLayoutConfigurator extends owl.Component {
56761
57483
  const fieldName = field ? getFieldDisplayName(field) : "";
56762
57484
  return measureDisplayTerms.descriptions[measureDisplay.type](fieldName);
56763
57485
  }
57486
+ getHugeDimensionErrorMessage(dimension) {
57487
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
57488
+ const possibleValues = pivot.getPossibleFieldValues(dimension);
57489
+ return possibleValues.length > 100
57490
+ ? _t("This dimension contains a lot of values (%s), and might slow down the pivot table.", possibleValues.length)
57491
+ : undefined;
57492
+ }
56764
57493
  }
56765
57494
 
56766
57495
  class PivotTitleSection extends owl.Component {
@@ -56934,6 +57663,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
56934
57663
  draft = null;
56935
57664
  notification = this.get(NotificationStore);
56936
57665
  alreadyNotified = false;
57666
+ alreadyNotifiedForPivotSize = false;
56937
57667
  constructor(get, pivotId) {
56938
57668
  super(get);
56939
57669
  this.pivotId = pivotId;
@@ -57052,6 +57782,16 @@ class PivotSidePanelStore extends SpreadsheetStore {
57052
57782
  sticky: true,
57053
57783
  });
57054
57784
  }
57785
+ const pivot = this.getters.getPivot(this.pivotId);
57786
+ const numberOfCells = pivot.isValid() ? pivot.getExpandedTableStructure().numberOfCells : 0;
57787
+ if (!this.alreadyNotifiedForPivotSize && numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
57788
+ this.alreadyNotifiedForPivotSize = true;
57789
+ this.notification.notifyUser({
57790
+ type: "warning",
57791
+ text: getPivotTooBigErrorMessage(numberOfCells, this.getters.getLocale()),
57792
+ sticky: true,
57793
+ });
57794
+ }
57055
57795
  }
57056
57796
  }
57057
57797
  discardPendingUpdate() {
@@ -58266,7 +59006,11 @@ sidePanelRegistry.add("PivotMeasureDisplayPanel", {
58266
59006
  try {
58267
59007
  // This will throw if the pivot or measure does not exist
58268
59008
  getters.getPivot(props.pivotId).getMeasure(props.measure.id);
58269
- return { isOpen: true, props, key: "pivot_measure_display" };
59009
+ return {
59010
+ isOpen: true,
59011
+ props,
59012
+ key: `pivot_measure_display_${props.pivotId}_${props.measure.id}`,
59013
+ };
58270
59014
  }
58271
59015
  catch (e) {
58272
59016
  return { isOpen: false };
@@ -58302,6 +59046,7 @@ const MIN_SHEET_VIEW_WIDTH = 150;
58302
59046
  class SidePanelStore extends SpreadsheetStore {
58303
59047
  mutators = [
58304
59048
  "open",
59049
+ "replace",
58305
59050
  "toggle",
58306
59051
  "close",
58307
59052
  "changePanelSize",
@@ -58363,8 +59108,7 @@ class SidePanelStore extends SpreadsheetStore {
58363
59108
  if (!state.isOpen) {
58364
59109
  return;
58365
59110
  }
58366
- const mainPanelKey = this.mainPanel ? this.getPanelKey(this.mainPanel) : undefined;
58367
- if (!this.mainPanel || !this.mainPanel.isPinned || mainPanelKey === state.key) {
59111
+ if (!this.mainPanel || !this.mainPanel.isPinned || this.mainPanelKey === state.key) {
58368
59112
  this._openPanel("mainPanel", newPanelInfo, state);
58369
59113
  return;
58370
59114
  }
@@ -58383,6 +59127,34 @@ class SidePanelStore extends SpreadsheetStore {
58383
59127
  }
58384
59128
  this._openPanel("secondaryPanel", newPanelInfo, state);
58385
59129
  }
59130
+ replace(componentTag, currentPanelKey, initialPanelProps = {}) {
59131
+ const newPanelInfo = { initialPanelProps, componentTag, size: DEFAULT_SIDE_PANEL_SIZE };
59132
+ const state = this.computeState(newPanelInfo);
59133
+ if (!state.isOpen) {
59134
+ return;
59135
+ }
59136
+ const ensureMainPanelExpanded = () => {
59137
+ if (this.mainPanel?.isCollapsed) {
59138
+ this.toggleCollapsePanel("mainPanel");
59139
+ }
59140
+ };
59141
+ // Close the current panel if the target panel is already open
59142
+ const isMainPanel = this.mainPanelKey === state.key;
59143
+ const isSecondaryPanel = this.secondaryPanelKey === state.key;
59144
+ if (isMainPanel && this.secondaryPanel) {
59145
+ this.close();
59146
+ ensureMainPanelExpanded();
59147
+ return;
59148
+ }
59149
+ if (isSecondaryPanel) {
59150
+ this.closeMainPanel();
59151
+ this.togglePinPanel();
59152
+ ensureMainPanelExpanded();
59153
+ return;
59154
+ }
59155
+ const targetPanel = this.mainPanelKey === currentPanelKey ? "mainPanel" : "secondaryPanel";
59156
+ this._openPanel(targetPanel, newPanelInfo, state);
59157
+ }
58386
59158
  _openPanel(panel, newPanel, state) {
58387
59159
  const currentPanel = this[panel];
58388
59160
  if (currentPanel && newPanel.componentTag !== currentPanel.componentTag) {
@@ -58442,11 +59214,9 @@ class SidePanelStore extends SpreadsheetStore {
58442
59214
  panelInfo.size = size;
58443
59215
  }
58444
59216
  resetPanelSize(panel) {
58445
- const panelInfo = this[panel];
58446
- if (!panelInfo) {
58447
- return;
59217
+ if (this[panel]) {
59218
+ this[panel].size = DEFAULT_SIDE_PANEL_SIZE;
58448
59219
  }
58449
- panelInfo.size = DEFAULT_SIDE_PANEL_SIZE;
58450
59220
  }
58451
59221
  togglePinPanel() {
58452
59222
  if (!this.mainPanel) {
@@ -79232,9 +80002,7 @@ class ClickableCellsStore extends SpreadsheetStore {
79232
80002
  }
79233
80003
  if (!(xc in clickableCells[sheetId])) {
79234
80004
  const clickableCell = this.findClickableItem(position);
79235
- if (clickableCell) {
79236
- clickableCells[sheetId][xc] = clickableCell;
79237
- }
80005
+ clickableCells[sheetId][xc] = clickableCell;
79238
80006
  }
79239
80007
  return clickableCells[sheetId][xc];
79240
80008
  }
@@ -81476,6 +82244,7 @@ class Spreadsheet extends owl.Component {
81476
82244
  loadLocales: this.model.config.external.loadLocales,
81477
82245
  isDashboard: () => this.model.getters.isDashboard(),
81478
82246
  openSidePanel: this.sidePanel.open.bind(this.sidePanel),
82247
+ replaceSidePanel: this.sidePanel.replace.bind(this.sidePanel),
81479
82248
  toggleSidePanel: this.sidePanel.toggle.bind(this.sidePanel),
81480
82249
  clipboard: this.env.clipboard || instantiateClipboard(),
81481
82250
  startCellEdition: (content) => this.composerFocusStore.focusActiveComposer({ content }),
@@ -86118,6 +86887,7 @@ const components = {
86118
86887
  ChartPanel,
86119
86888
  ChartFigure,
86120
86889
  ChartJsComponent,
86890
+ ZoomableChartJsComponent,
86121
86891
  Grid,
86122
86892
  GridOverlay,
86123
86893
  ScorecardChart,
@@ -86126,6 +86896,7 @@ const components = {
86126
86896
  PieChartDesignPanel,
86127
86897
  GenericChartConfigPanel,
86128
86898
  ChartWithAxisDesignPanel,
86899
+ GenericZoomableChartDesignPanel,
86129
86900
  LineChartDesignPanel,
86130
86901
  GaugeChartConfigPanel,
86131
86902
  GaugeChartDesignPanel,
@@ -86255,6 +87026,6 @@ exports.tokenColors = tokenColors;
86255
87026
  exports.tokenize = tokenize;
86256
87027
 
86257
87028
 
86258
- __info__.version = "18.5.0-alpha.7";
86259
- __info__.date = "2025-08-08T11:00:44.117Z";
86260
- __info__.hash = "1019d55";
87029
+ __info__.version = "18.5.0-alpha.8";
87030
+ __info__.date = "2025-08-18T08:17:58.775Z";
87031
+ __info__.hash = "994f1cb";