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