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