@odoo/o-spreadsheet 19.1.0-alpha.7 → 19.1.0-alpha.9
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.
- package/dist/o-spreadsheet-engine.d.ts +19 -22
- package/dist/o-spreadsheet-engine.esm.js +489 -198
- package/dist/o-spreadsheet-engine.iife.js +489 -198
- package/dist/o-spreadsheet-engine.min.iife.js +313 -313
- package/dist/o-spreadsheet.d.ts +259 -171
- package/dist/o_spreadsheet.esm.js +804 -424
- package/dist/o_spreadsheet.iife.js +804 -424
- package/dist/o_spreadsheet.min.iife.js +317 -317
- package/dist/o_spreadsheet.xml +147 -37
- package/package.json +1 -1
|
@@ -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 19.1.0-alpha.
|
|
6
|
-
* @date 2025-10-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.1.0-alpha.9
|
|
6
|
+
* @date 2025-10-23T11:12:55.400Z
|
|
7
|
+
* @hash bd756dd
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports, owl) {
|
|
@@ -488,8 +488,17 @@
|
|
|
488
488
|
/**
|
|
489
489
|
* Returns a function, that, as long as it continues to be invoked, will not
|
|
490
490
|
* be triggered. The function will be called after it stops being called for
|
|
491
|
-
* N milliseconds. If `immediate` is passed,
|
|
492
|
-
*
|
|
491
|
+
* N milliseconds. If `immediate` is passed, the function is called is called
|
|
492
|
+
* immediately on the first call and the debouncing is triggered starting the second
|
|
493
|
+
* call in the defined time window.
|
|
494
|
+
*
|
|
495
|
+
* Example:
|
|
496
|
+
* debouncedFunction = debounce(() => console.log('Hello!'), 250);
|
|
497
|
+
* debouncedFunction(); debouncedFunction(); // Will log 'Hello!' after 250ms
|
|
498
|
+
*
|
|
499
|
+
* debouncedFunction = debounce(() => console.log('Hello!'), 250, true);
|
|
500
|
+
* debouncedFunction(); debouncedFunction(); // Will log 'Hello!' and relog it after 250ms
|
|
501
|
+
*
|
|
493
502
|
*
|
|
494
503
|
* Also decorate the argument function with two methods: stopDebounce and isDebouncePending.
|
|
495
504
|
*
|
|
@@ -497,21 +506,21 @@
|
|
|
497
506
|
*/
|
|
498
507
|
function debounce(func, wait, immediate) {
|
|
499
508
|
let timeout = undefined;
|
|
509
|
+
let firstCalled = false;
|
|
500
510
|
const debounced = function () {
|
|
501
511
|
const context = this;
|
|
502
512
|
const args = Array.from(arguments);
|
|
513
|
+
if (!firstCalled && immediate) {
|
|
514
|
+
firstCalled = true;
|
|
515
|
+
return func.apply(context, args);
|
|
516
|
+
}
|
|
503
517
|
function later() {
|
|
504
518
|
timeout = undefined;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
519
|
+
firstCalled = false;
|
|
520
|
+
func.apply(context, args);
|
|
508
521
|
}
|
|
509
|
-
const callNow = immediate && !timeout;
|
|
510
522
|
clearTimeout(timeout);
|
|
511
523
|
timeout = setTimeout(later, wait);
|
|
512
|
-
if (callNow) {
|
|
513
|
-
func.apply(context, args);
|
|
514
|
-
}
|
|
515
524
|
};
|
|
516
525
|
debounced.isDebouncePending = () => timeout !== undefined;
|
|
517
526
|
debounced.stopDebounce = () => {
|
|
@@ -1154,6 +1163,29 @@
|
|
|
1154
1163
|
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
1155
1164
|
}
|
|
1156
1165
|
}
|
|
1166
|
+
function profilesContainsZone(profilesStartingPosition, profiles, zone) {
|
|
1167
|
+
const leftValue = zone.left;
|
|
1168
|
+
const rightValue = zone.right;
|
|
1169
|
+
const topValue = zone.top;
|
|
1170
|
+
const bottomValue = zone.bottom + 1;
|
|
1171
|
+
const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
|
|
1172
|
+
const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
|
|
1173
|
+
if (leftIndex === -1 || rightIndex === -1) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
1177
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
1178
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
|
|
1179
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
|
|
1180
|
+
if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1157
1189
|
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
1158
1190
|
if (value === undefined) {
|
|
1159
1191
|
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
@@ -1238,7 +1270,18 @@
|
|
|
1238
1270
|
}
|
|
1239
1271
|
// add the top and bottom value to the profile and
|
|
1240
1272
|
// remove all information between the top and bottom index
|
|
1241
|
-
|
|
1273
|
+
const toDelete = bottomSuccIndex - topPredIndex - 1;
|
|
1274
|
+
const toInsert = newPoints.length;
|
|
1275
|
+
const start = topPredIndex + 1;
|
|
1276
|
+
// fast path and slow path
|
|
1277
|
+
if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
|
|
1278
|
+
// fast path: we just need to replace the last element
|
|
1279
|
+
profile[start] = newPoints[0] ?? newPoints[1];
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
// equivalent but slower and with memory allocation
|
|
1283
|
+
profile.splice(start, toDelete, ...newPoints);
|
|
1284
|
+
}
|
|
1242
1285
|
}
|
|
1243
1286
|
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
1244
1287
|
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
@@ -1277,8 +1320,10 @@
|
|
|
1277
1320
|
left,
|
|
1278
1321
|
bottom,
|
|
1279
1322
|
right,
|
|
1280
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
1281
1323
|
};
|
|
1324
|
+
if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
|
|
1325
|
+
profileZone.hasHeader = true;
|
|
1326
|
+
}
|
|
1282
1327
|
let findCorrespondingZone = false;
|
|
1283
1328
|
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
1284
1329
|
const pendingZone = pendingZones[j];
|
|
@@ -1763,17 +1808,6 @@
|
|
|
1763
1808
|
}
|
|
1764
1809
|
return [leftColumnZone, rightPartZone];
|
|
1765
1810
|
}
|
|
1766
|
-
function aggregatePositionsToZones(positions) {
|
|
1767
|
-
const result = {};
|
|
1768
|
-
for (const position of positions) {
|
|
1769
|
-
result[position.sheetId] ??= [];
|
|
1770
|
-
result[position.sheetId].push(positionToZone(position));
|
|
1771
|
-
}
|
|
1772
|
-
for (const sheetId in result) {
|
|
1773
|
-
result[sheetId] = recomputeZones(result[sheetId]);
|
|
1774
|
-
}
|
|
1775
|
-
return result;
|
|
1776
|
-
}
|
|
1777
1811
|
/**
|
|
1778
1812
|
* Array of all positions in the zone.
|
|
1779
1813
|
*/
|
|
@@ -14882,6 +14916,13 @@
|
|
|
14882
14916
|
.add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
|
|
14883
14917
|
.add("second_number", nullHandlerDecorator(secondNumberAdapter));
|
|
14884
14918
|
|
|
14919
|
+
const DEFAULT_PIVOT_STYLE = {
|
|
14920
|
+
displayTotals: true,
|
|
14921
|
+
displayColumnHeaders: true,
|
|
14922
|
+
displayMeasuresRow: true,
|
|
14923
|
+
numberOfRows: Number.MAX_VALUE,
|
|
14924
|
+
numberOfColumns: Number.MAX_VALUE,
|
|
14925
|
+
};
|
|
14885
14926
|
const AGGREGATOR_NAMES = {
|
|
14886
14927
|
count: _t$1("Count"),
|
|
14887
14928
|
count_distinct: _t$1("Count Distinct"),
|
|
@@ -15215,6 +15256,25 @@
|
|
|
15215
15256
|
pivot: { ...definition, collapsedDomains: newDomains },
|
|
15216
15257
|
});
|
|
15217
15258
|
}
|
|
15259
|
+
function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
|
|
15260
|
+
const style = definition.style;
|
|
15261
|
+
const numberOfRows = rowCountArg !== undefined
|
|
15262
|
+
? toNumber(rowCountArg, locale)
|
|
15263
|
+
: style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
|
|
15264
|
+
const numberOfColumns = columnCountArg !== undefined
|
|
15265
|
+
? toNumber(columnCountArg, locale)
|
|
15266
|
+
: style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
|
|
15267
|
+
const displayTotals = includeTotalArg !== undefined
|
|
15268
|
+
? toBoolean(includeTotalArg)
|
|
15269
|
+
: style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
|
|
15270
|
+
const displayColumnHeaders = includeColumnHeadersArg !== undefined
|
|
15271
|
+
? toBoolean(includeColumnHeadersArg)
|
|
15272
|
+
: style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
|
|
15273
|
+
const displayMeasuresRow = includeMeasuresRowArg !== undefined
|
|
15274
|
+
? toBoolean(includeMeasuresRowArg)
|
|
15275
|
+
: style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
|
|
15276
|
+
return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
|
|
15277
|
+
}
|
|
15218
15278
|
|
|
15219
15279
|
/**
|
|
15220
15280
|
* Get the pivot ID from the formula pivot ID.
|
|
@@ -15829,24 +15889,18 @@
|
|
|
15829
15889
|
arg("column_count (number, optional)", _t$1("number of columns")),
|
|
15830
15890
|
arg("include_measure_titles (boolean, default=TRUE)", _t$1("Whether to include the measure titles row or not.")),
|
|
15831
15891
|
],
|
|
15832
|
-
compute: function (pivotFormulaId, rowCount
|
|
15892
|
+
compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
|
|
15833
15893
|
const _pivotFormulaId = toString(pivotFormulaId);
|
|
15834
|
-
const
|
|
15835
|
-
|
|
15894
|
+
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15895
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
15896
|
+
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15897
|
+
const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
|
|
15898
|
+
if (pivotStyle.numberOfRows < 0) {
|
|
15836
15899
|
return new EvaluationError(_t$1("The number of rows must be positive."));
|
|
15837
15900
|
}
|
|
15838
|
-
|
|
15839
|
-
if (_columnCount < 0) {
|
|
15901
|
+
if (pivotStyle.numberOfColumns < 0) {
|
|
15840
15902
|
return new EvaluationError(_t$1("The number of columns must be positive."));
|
|
15841
15903
|
}
|
|
15842
|
-
const visibilityOptions = {
|
|
15843
|
-
displayColumnHeaders: toBoolean(includeColumnHeaders),
|
|
15844
|
-
displayTotals: toBoolean(includeTotal),
|
|
15845
|
-
displayMeasuresRow: toBoolean(includeMeasureTitles),
|
|
15846
|
-
};
|
|
15847
|
-
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15848
|
-
const pivot = this.getters.getPivot(pivotId);
|
|
15849
|
-
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15850
15904
|
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
15851
15905
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
15852
15906
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
@@ -15857,20 +15911,20 @@
|
|
|
15857
15911
|
if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
|
|
15858
15912
|
return new EvaluationError(getPivotTooBigErrorMessage$1(table.numberOfCells, this.locale));
|
|
15859
15913
|
}
|
|
15860
|
-
const cells = table.getPivotCells(
|
|
15914
|
+
const cells = table.getPivotCells(pivotStyle);
|
|
15861
15915
|
let headerRows = 0;
|
|
15862
|
-
if (
|
|
15916
|
+
if (pivotStyle.displayColumnHeaders) {
|
|
15863
15917
|
headerRows = table.columns.length - 1;
|
|
15864
15918
|
}
|
|
15865
|
-
if (
|
|
15919
|
+
if (pivotStyle.displayMeasuresRow) {
|
|
15866
15920
|
headerRows++;
|
|
15867
15921
|
}
|
|
15868
15922
|
const pivotTitle = this.getters.getPivotName(pivotId);
|
|
15869
|
-
const tableHeight = Math.min(headerRows +
|
|
15923
|
+
const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
|
|
15870
15924
|
if (tableHeight === 0) {
|
|
15871
15925
|
return [[{ value: pivotTitle }]];
|
|
15872
15926
|
}
|
|
15873
|
-
const tableWidth = Math.min(1 +
|
|
15927
|
+
const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
|
|
15874
15928
|
const result = [];
|
|
15875
15929
|
for (const col of range$1(0, tableWidth)) {
|
|
15876
15930
|
result[col] = [];
|
|
@@ -15893,7 +15947,7 @@
|
|
|
15893
15947
|
}
|
|
15894
15948
|
}
|
|
15895
15949
|
}
|
|
15896
|
-
if (
|
|
15950
|
+
if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
|
|
15897
15951
|
result[0][0] = { value: pivotTitle };
|
|
15898
15952
|
}
|
|
15899
15953
|
return result;
|
|
@@ -19495,6 +19549,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
19495
19549
|
}
|
|
19496
19550
|
return parts;
|
|
19497
19551
|
}
|
|
19552
|
+
function positionToBoundedRange(position) {
|
|
19553
|
+
const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
|
|
19554
|
+
return { sheetId: position.sheetId, zone };
|
|
19555
|
+
}
|
|
19498
19556
|
/**
|
|
19499
19557
|
* Check that a zone is valid regarding the order of top-bottom and left-right.
|
|
19500
19558
|
* Left should be smaller than right, top should be smaller than bottom.
|
|
@@ -21361,6 +21419,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21361
21419
|
backgroundColor,
|
|
21362
21420
|
yAxisID: definition.horizontal ? "y" : definition.dataSets?.[index].yAxisId || "y",
|
|
21363
21421
|
xAxisID: "x",
|
|
21422
|
+
barPercentage: 0.9,
|
|
21423
|
+
categoryPercentage: dataSetsValues.length > 1 ? 0.8 : 1,
|
|
21424
|
+
borderRadius: 2,
|
|
21364
21425
|
};
|
|
21365
21426
|
dataSets.push(dataset);
|
|
21366
21427
|
const trendConfig = definition.dataSets?.[index].trend;
|
|
@@ -21489,6 +21550,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21489
21550
|
const dataSets = [];
|
|
21490
21551
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
21491
21552
|
const trendDatasets = [];
|
|
21553
|
+
const barDatasets = dataSetsValues.filter((_, i) => (definition.dataSets?.[i].type ?? "line") === "bar");
|
|
21492
21554
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
21493
21555
|
let { label, data, hidden } = dataSetsValues[index];
|
|
21494
21556
|
label = definition.dataSets?.[index].label || label;
|
|
@@ -21507,6 +21569,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21507
21569
|
order: type === "bar" ? dataSetsValues.length + index : index,
|
|
21508
21570
|
pointRadius: definition.hideDataMarkers ? 0 : LINE_DATA_POINT_RADIUS,
|
|
21509
21571
|
};
|
|
21572
|
+
if (dataset.type === "bar") {
|
|
21573
|
+
dataset.barPercentage = 0.9;
|
|
21574
|
+
dataset.categoryPercentage = barDatasets.length > 1 ? 0.8 : 1;
|
|
21575
|
+
dataset.borderRadius = 2;
|
|
21576
|
+
}
|
|
21510
21577
|
dataSets.push(dataset);
|
|
21511
21578
|
const trendConfig = definition.dataSets?.[index].trend;
|
|
21512
21579
|
const trendData = args.trendDataSetsValues?.[index];
|
|
@@ -33489,6 +33556,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33489
33556
|
return data;
|
|
33490
33557
|
}
|
|
33491
33558
|
const figureIds = new Set();
|
|
33559
|
+
const chartIds = new Set();
|
|
33492
33560
|
const uuidGenerator = new UuidGenerator();
|
|
33493
33561
|
for (const sheet of data.sheets || []) {
|
|
33494
33562
|
for (const figure of sheet.figures || []) {
|
|
@@ -33496,6 +33564,12 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33496
33564
|
figure.id += uuidGenerator.smallUuid();
|
|
33497
33565
|
}
|
|
33498
33566
|
figureIds.add(figure.id);
|
|
33567
|
+
if (figure.tag === "chart") {
|
|
33568
|
+
if (chartIds.has(figure.data?.chartId)) {
|
|
33569
|
+
figure.data.chartId += uuidGenerator.smallUuid();
|
|
33570
|
+
}
|
|
33571
|
+
chartIds.add(figure.data?.chartId);
|
|
33572
|
+
}
|
|
33499
33573
|
}
|
|
33500
33574
|
}
|
|
33501
33575
|
data.uniqueFigureIds = true;
|
|
@@ -33863,11 +33937,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33863
33937
|
* @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
|
|
33864
33938
|
*/
|
|
33865
33939
|
adaptRanges(applyChange, sheetId, sheetName) { }
|
|
33866
|
-
/**
|
|
33867
|
-
* Implement this method to clean unused external resources, such as images
|
|
33868
|
-
* stored on a server which have been deleted.
|
|
33869
|
-
*/
|
|
33870
|
-
garbageCollectExternalResources() { }
|
|
33871
33940
|
}
|
|
33872
33941
|
|
|
33873
33942
|
class BordersPlugin extends CorePlugin {
|
|
@@ -37711,17 +37780,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
37711
37780
|
break;
|
|
37712
37781
|
}
|
|
37713
37782
|
}
|
|
37714
|
-
/**
|
|
37715
|
-
* Delete unused images from the file store
|
|
37716
|
-
*/
|
|
37717
|
-
garbageCollectExternalResources() {
|
|
37718
|
-
const images = new Set(this.getAllImages().map((image) => image.path));
|
|
37719
|
-
for (const path of this.syncedImages) {
|
|
37720
|
-
if (!images.has(path)) {
|
|
37721
|
-
this.fileStore?.delete(path);
|
|
37722
|
-
}
|
|
37723
|
-
}
|
|
37724
|
-
}
|
|
37725
37783
|
// ---------------------------------------------------------------------------
|
|
37726
37784
|
// Getters
|
|
37727
37785
|
// ---------------------------------------------------------------------------
|
|
@@ -37783,13 +37841,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
37783
37841
|
sheet.images = [...sheet.images, ...images];
|
|
37784
37842
|
}
|
|
37785
37843
|
}
|
|
37786
|
-
getAllImages() {
|
|
37787
|
-
const images = [];
|
|
37788
|
-
for (const sheetId in this.images) {
|
|
37789
|
-
images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined$1));
|
|
37790
|
-
}
|
|
37791
|
-
return images;
|
|
37792
|
-
}
|
|
37793
37844
|
}
|
|
37794
37845
|
|
|
37795
37846
|
class MergePlugin extends CorePlugin {
|
|
@@ -38562,26 +38613,22 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38562
38613
|
getNumberOfDataColumns() {
|
|
38563
38614
|
return this.columns.at(-1)?.length || 0;
|
|
38564
38615
|
}
|
|
38565
|
-
getSkippedRows(
|
|
38616
|
+
getSkippedRows(pivotStyle) {
|
|
38566
38617
|
const skippedRows = new Set();
|
|
38567
|
-
if (!
|
|
38618
|
+
if (!pivotStyle.displayColumnHeaders) {
|
|
38568
38619
|
for (let i = 0; i < this.columns.length - 1; i++) {
|
|
38569
38620
|
skippedRows.add(i);
|
|
38570
38621
|
}
|
|
38571
38622
|
}
|
|
38572
|
-
if (!
|
|
38623
|
+
if (!pivotStyle.displayMeasuresRow) {
|
|
38573
38624
|
skippedRows.add(this.columns.length - 1);
|
|
38574
38625
|
}
|
|
38575
38626
|
return skippedRows;
|
|
38576
38627
|
}
|
|
38577
|
-
getPivotCells(
|
|
38578
|
-
|
|
38579
|
-
displayTotals: true,
|
|
38580
|
-
displayMeasuresRow: true,
|
|
38581
|
-
}) {
|
|
38582
|
-
const key = JSON.stringify(visibilityOptions);
|
|
38628
|
+
getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
|
|
38629
|
+
const key = JSON.stringify(pivotStyle);
|
|
38583
38630
|
if (!this.pivotCells[key]) {
|
|
38584
|
-
const { displayTotals } =
|
|
38631
|
+
const { displayTotals } = pivotStyle;
|
|
38585
38632
|
const numberOfDataRows = this.rows.length;
|
|
38586
38633
|
const numberOfDataColumns = this.getNumberOfDataColumns();
|
|
38587
38634
|
let pivotHeight = this.columns.length + numberOfDataRows;
|
|
@@ -38593,7 +38640,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38593
38640
|
pivotWidth -= this.measures.length;
|
|
38594
38641
|
}
|
|
38595
38642
|
const domainArray = [];
|
|
38596
|
-
const skippedRows = this.getSkippedRows(
|
|
38643
|
+
const skippedRows = this.getSkippedRows(pivotStyle);
|
|
38597
38644
|
for (let col = 0; col < pivotWidth; col++) {
|
|
38598
38645
|
domainArray.push([]);
|
|
38599
38646
|
for (let row = 0; row < pivotHeight; row++) {
|
|
@@ -41685,6 +41732,281 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41685
41732
|
}
|
|
41686
41733
|
}
|
|
41687
41734
|
|
|
41735
|
+
class ZoneSet {
|
|
41736
|
+
profilesStartingPosition = [0];
|
|
41737
|
+
profiles = new Map([[0, []]]);
|
|
41738
|
+
constructor(zones = []) {
|
|
41739
|
+
for (const zone of zones) {
|
|
41740
|
+
this.add(zone);
|
|
41741
|
+
}
|
|
41742
|
+
}
|
|
41743
|
+
isEmpty() {
|
|
41744
|
+
return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
|
|
41745
|
+
}
|
|
41746
|
+
add(zone) {
|
|
41747
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
|
|
41748
|
+
}
|
|
41749
|
+
delete(zone) {
|
|
41750
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
|
|
41751
|
+
}
|
|
41752
|
+
has(zone) {
|
|
41753
|
+
return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
|
|
41754
|
+
}
|
|
41755
|
+
difference(other) {
|
|
41756
|
+
const result = this.copy();
|
|
41757
|
+
for (const zone of other) {
|
|
41758
|
+
result.delete(zone);
|
|
41759
|
+
}
|
|
41760
|
+
return result;
|
|
41761
|
+
}
|
|
41762
|
+
copy() {
|
|
41763
|
+
const result = new ZoneSet();
|
|
41764
|
+
result.profilesStartingPosition = [...this.profilesStartingPosition];
|
|
41765
|
+
result.profiles = new Map();
|
|
41766
|
+
for (const [key, value] of this.profiles) {
|
|
41767
|
+
result.profiles.set(key, [...value]);
|
|
41768
|
+
}
|
|
41769
|
+
return result;
|
|
41770
|
+
}
|
|
41771
|
+
size() {
|
|
41772
|
+
let size = 0;
|
|
41773
|
+
for (const profile of this.profiles.values()) {
|
|
41774
|
+
size += profile.length;
|
|
41775
|
+
}
|
|
41776
|
+
return size / 2;
|
|
41777
|
+
}
|
|
41778
|
+
/**
|
|
41779
|
+
* iterator of all the zones in the ZoneSet
|
|
41780
|
+
*/
|
|
41781
|
+
[Symbol.iterator]() {
|
|
41782
|
+
return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
|
|
41783
|
+
}
|
|
41784
|
+
}
|
|
41785
|
+
|
|
41786
|
+
class RangeSet {
|
|
41787
|
+
setsBySheetId = {};
|
|
41788
|
+
constructor(ranges = []) {
|
|
41789
|
+
for (const range of ranges) {
|
|
41790
|
+
this.add(range);
|
|
41791
|
+
}
|
|
41792
|
+
}
|
|
41793
|
+
add(range) {
|
|
41794
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41795
|
+
this.setsBySheetId[range.sheetId] = new ZoneSet();
|
|
41796
|
+
}
|
|
41797
|
+
this.setsBySheetId[range.sheetId].add(range.zone);
|
|
41798
|
+
}
|
|
41799
|
+
addMany(ranges) {
|
|
41800
|
+
for (const range of ranges) {
|
|
41801
|
+
this.add(range);
|
|
41802
|
+
}
|
|
41803
|
+
}
|
|
41804
|
+
addPosition(position) {
|
|
41805
|
+
this.add(positionToBoundedRange(position));
|
|
41806
|
+
}
|
|
41807
|
+
addManyPositions(positions) {
|
|
41808
|
+
for (const position of positions) {
|
|
41809
|
+
this.addPosition(position);
|
|
41810
|
+
}
|
|
41811
|
+
}
|
|
41812
|
+
has(range) {
|
|
41813
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41814
|
+
return false;
|
|
41815
|
+
}
|
|
41816
|
+
return this.setsBySheetId[range.sheetId].has(range.zone);
|
|
41817
|
+
}
|
|
41818
|
+
hasPosition(position) {
|
|
41819
|
+
return this.has(positionToBoundedRange(position));
|
|
41820
|
+
}
|
|
41821
|
+
delete(range) {
|
|
41822
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41823
|
+
return;
|
|
41824
|
+
}
|
|
41825
|
+
this.setsBySheetId[range.sheetId].delete(range.zone);
|
|
41826
|
+
}
|
|
41827
|
+
deleteMany(ranges) {
|
|
41828
|
+
for (const range of ranges) {
|
|
41829
|
+
this.delete(range);
|
|
41830
|
+
}
|
|
41831
|
+
}
|
|
41832
|
+
deleteManyPositions(positions) {
|
|
41833
|
+
for (const position of positions) {
|
|
41834
|
+
this.delete(positionToBoundedRange(position));
|
|
41835
|
+
}
|
|
41836
|
+
}
|
|
41837
|
+
difference(other) {
|
|
41838
|
+
const result = new RangeSet();
|
|
41839
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41840
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
|
|
41841
|
+
}
|
|
41842
|
+
for (const sheetId in other.setsBySheetId) {
|
|
41843
|
+
if (result.setsBySheetId[sheetId]) {
|
|
41844
|
+
result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
|
|
41845
|
+
}
|
|
41846
|
+
}
|
|
41847
|
+
return result;
|
|
41848
|
+
}
|
|
41849
|
+
copy() {
|
|
41850
|
+
const result = new RangeSet();
|
|
41851
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41852
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
|
|
41853
|
+
}
|
|
41854
|
+
return result;
|
|
41855
|
+
}
|
|
41856
|
+
clear() {
|
|
41857
|
+
this.setsBySheetId = {};
|
|
41858
|
+
}
|
|
41859
|
+
size() {
|
|
41860
|
+
let size = 0;
|
|
41861
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41862
|
+
size += this.setsBySheetId[sheetId].size();
|
|
41863
|
+
}
|
|
41864
|
+
return size;
|
|
41865
|
+
}
|
|
41866
|
+
isEmpty() {
|
|
41867
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41868
|
+
if (!this.setsBySheetId[sheetId].isEmpty()) {
|
|
41869
|
+
return false;
|
|
41870
|
+
}
|
|
41871
|
+
}
|
|
41872
|
+
return true;
|
|
41873
|
+
}
|
|
41874
|
+
/**
|
|
41875
|
+
* iterator of all the ranges in the RangeSet
|
|
41876
|
+
*/
|
|
41877
|
+
[Symbol.iterator]() {
|
|
41878
|
+
const result = [];
|
|
41879
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41880
|
+
for (const zone of this.setsBySheetId[sheetId]) {
|
|
41881
|
+
result.push({ sheetId: sheetId, zone });
|
|
41882
|
+
}
|
|
41883
|
+
}
|
|
41884
|
+
return result[Symbol.iterator]();
|
|
41885
|
+
}
|
|
41886
|
+
}
|
|
41887
|
+
|
|
41888
|
+
/**
|
|
41889
|
+
* R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
|
|
41890
|
+
* Ranges associated to the exact same bounding box are grouped together
|
|
41891
|
+
* to reduce the number of nodes in the R-tree.
|
|
41892
|
+
*/
|
|
41893
|
+
class DependenciesRTree {
|
|
41894
|
+
rTree;
|
|
41895
|
+
constructor(items = []) {
|
|
41896
|
+
const compactedBoxes = groupSameBoundingBoxes(items);
|
|
41897
|
+
this.rTree = new SpreadsheetRTree(compactedBoxes);
|
|
41898
|
+
}
|
|
41899
|
+
insert(item) {
|
|
41900
|
+
const data = this.rTree.search(item.boundingBox);
|
|
41901
|
+
const itemBoundingBox = item.boundingBox;
|
|
41902
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
41903
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
41904
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
41905
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
41906
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
41907
|
+
if (exactBoundingBox) {
|
|
41908
|
+
exactBoundingBox.data.add(item.data);
|
|
41909
|
+
}
|
|
41910
|
+
else {
|
|
41911
|
+
this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
|
|
41912
|
+
}
|
|
41913
|
+
}
|
|
41914
|
+
search({ zone, sheetId }) {
|
|
41915
|
+
const results = new RangeSet();
|
|
41916
|
+
for (const { data } of this.rTree.search({ zone, sheetId })) {
|
|
41917
|
+
results.addMany(data);
|
|
41918
|
+
}
|
|
41919
|
+
return results;
|
|
41920
|
+
}
|
|
41921
|
+
remove(item) {
|
|
41922
|
+
const data = this.rTree.search(item.boundingBox);
|
|
41923
|
+
const itemBoundingBox = item.boundingBox;
|
|
41924
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
41925
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
41926
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
41927
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
41928
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
41929
|
+
if (exactBoundingBox) {
|
|
41930
|
+
exactBoundingBox.data.delete(item.data);
|
|
41931
|
+
}
|
|
41932
|
+
else {
|
|
41933
|
+
this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
|
|
41934
|
+
}
|
|
41935
|
+
}
|
|
41936
|
+
}
|
|
41937
|
+
/**
|
|
41938
|
+
* Group together all formulas pointing to the exact same dependency (bounding box).
|
|
41939
|
+
* The goal is to optimize the following case:
|
|
41940
|
+
* - if any cell in B1:B1000 changes, C1 must be recomputed
|
|
41941
|
+
* - if any cell in B1:B1000 changes, C2 must be recomputed
|
|
41942
|
+
* - if any cell in B1:B1000 changes, C3 must be recomputed
|
|
41943
|
+
* ...
|
|
41944
|
+
* - if any cell in B1:B1000 changes, C1000 must be recomputed
|
|
41945
|
+
*
|
|
41946
|
+
* Instead of having 1000 entries in the R-tree, we want to have a single entry
|
|
41947
|
+
* with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
|
|
41948
|
+
*/
|
|
41949
|
+
function groupSameBoundingBoxes(items) {
|
|
41950
|
+
// Important: this function must be as fast as possible. It is on the evaluation hot path.
|
|
41951
|
+
let maxCol = 0;
|
|
41952
|
+
let maxRow = 0;
|
|
41953
|
+
for (let i = 0; i < items.length; i++) {
|
|
41954
|
+
const zone = items[i].boundingBox.zone;
|
|
41955
|
+
if (zone.right > maxCol) {
|
|
41956
|
+
maxCol = zone.right;
|
|
41957
|
+
}
|
|
41958
|
+
if (zone.bottom > maxRow) {
|
|
41959
|
+
maxRow = zone.bottom;
|
|
41960
|
+
}
|
|
41961
|
+
}
|
|
41962
|
+
maxCol += 1;
|
|
41963
|
+
maxRow += 1;
|
|
41964
|
+
// in most real-world cases, we can use a fast numeric key
|
|
41965
|
+
// but if the zones are too far right or bottom, we fallback to a slower string key
|
|
41966
|
+
const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
|
|
41967
|
+
const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
|
|
41968
|
+
if (!useFastKey) {
|
|
41969
|
+
console.warn("Max col/row size exceeded, using slow zone key");
|
|
41970
|
+
}
|
|
41971
|
+
const groupedByBBox = {};
|
|
41972
|
+
for (const item of items) {
|
|
41973
|
+
const sheetId = item.boundingBox.sheetId;
|
|
41974
|
+
if (!groupedByBBox[sheetId]) {
|
|
41975
|
+
groupedByBBox[sheetId] = {};
|
|
41976
|
+
}
|
|
41977
|
+
const bBox = item.boundingBox.zone;
|
|
41978
|
+
let bBoxKey = 0;
|
|
41979
|
+
if (useFastKey) {
|
|
41980
|
+
bBoxKey =
|
|
41981
|
+
bBox.left +
|
|
41982
|
+
bBox.top * maxCol +
|
|
41983
|
+
bBox.right * maxCol * maxRow +
|
|
41984
|
+
bBox.bottom * maxCol * maxRow * maxCol;
|
|
41985
|
+
}
|
|
41986
|
+
else {
|
|
41987
|
+
bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
|
|
41988
|
+
}
|
|
41989
|
+
if (groupedByBBox[sheetId][bBoxKey]) {
|
|
41990
|
+
const ranges = groupedByBBox[sheetId][bBoxKey].data;
|
|
41991
|
+
ranges.add(item.data);
|
|
41992
|
+
}
|
|
41993
|
+
else {
|
|
41994
|
+
groupedByBBox[sheetId][bBoxKey] = {
|
|
41995
|
+
boundingBox: item.boundingBox,
|
|
41996
|
+
data: new RangeSet([item.data]),
|
|
41997
|
+
};
|
|
41998
|
+
}
|
|
41999
|
+
}
|
|
42000
|
+
const result = [];
|
|
42001
|
+
for (const sheetId in groupedByBBox) {
|
|
42002
|
+
const map = groupedByBBox[sheetId];
|
|
42003
|
+
for (const key in map) {
|
|
42004
|
+
result.push(map[key]);
|
|
42005
|
+
}
|
|
42006
|
+
}
|
|
42007
|
+
return result;
|
|
42008
|
+
}
|
|
42009
|
+
|
|
41688
42010
|
/**
|
|
41689
42011
|
* Implementation of a dependency Graph.
|
|
41690
42012
|
* The graph is used to evaluate the cells in the correct
|
|
@@ -41693,12 +42015,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41693
42015
|
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
41694
42016
|
*/
|
|
41695
42017
|
class FormulaDependencyGraph {
|
|
41696
|
-
createEmptyPositionSet;
|
|
41697
42018
|
dependencies = new PositionMap();
|
|
41698
42019
|
rTree;
|
|
41699
|
-
constructor(
|
|
41700
|
-
this.
|
|
41701
|
-
this.rTree = new SpreadsheetRTree(data);
|
|
42020
|
+
constructor(data = []) {
|
|
42021
|
+
this.rTree = new DependenciesRTree(data);
|
|
41702
42022
|
}
|
|
41703
42023
|
removeAllDependencies(formulaPosition) {
|
|
41704
42024
|
const ranges = this.dependencies.get(formulaPosition);
|
|
@@ -41712,7 +42032,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41712
42032
|
}
|
|
41713
42033
|
addDependencies(formulaPosition, dependencies) {
|
|
41714
42034
|
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
41715
|
-
data:
|
|
42035
|
+
data: {
|
|
42036
|
+
sheetId: formulaPosition.sheetId,
|
|
42037
|
+
zone: positionToZone(formulaPosition),
|
|
42038
|
+
},
|
|
41716
42039
|
boundingBox: {
|
|
41717
42040
|
zone,
|
|
41718
42041
|
sheetId,
|
|
@@ -41730,46 +42053,20 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41730
42053
|
}
|
|
41731
42054
|
}
|
|
41732
42055
|
/**
|
|
41733
|
-
* Return all the cells that depend on the provided ranges
|
|
41734
|
-
* in the correct order they should be evaluated.
|
|
41735
|
-
* This is called a topological ordering (excluding cycles)
|
|
42056
|
+
* Return all the cells that depend on the provided ranges.
|
|
41736
42057
|
*/
|
|
41737
|
-
getCellsDependingOn(ranges,
|
|
41738
|
-
|
|
42058
|
+
getCellsDependingOn(ranges, visited = new RangeSet()) {
|
|
42059
|
+
visited = visited.copy();
|
|
41739
42060
|
const queue = Array.from(ranges).reverse();
|
|
41740
42061
|
while (queue.length > 0) {
|
|
41741
42062
|
const range = queue.pop();
|
|
41742
|
-
|
|
41743
|
-
const
|
|
41744
|
-
|
|
41745
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41746
|
-
visited.add({ sheetId, col, row });
|
|
41747
|
-
}
|
|
41748
|
-
}
|
|
41749
|
-
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
41750
|
-
const nextInQueue = {};
|
|
41751
|
-
for (const position of impactedPositions) {
|
|
41752
|
-
if (!visited.has(position) && !ignore.has(position)) {
|
|
41753
|
-
if (!nextInQueue[position.sheetId]) {
|
|
41754
|
-
nextInQueue[position.sheetId] = [];
|
|
41755
|
-
}
|
|
41756
|
-
nextInQueue[position.sheetId].push(positionToZone(position));
|
|
41757
|
-
}
|
|
41758
|
-
}
|
|
41759
|
-
for (const sheetId in nextInQueue) {
|
|
41760
|
-
const zones = recomputeZones(nextInQueue[sheetId], []);
|
|
41761
|
-
queue.push(...zones.map((zone) => ({ sheetId, zone })));
|
|
41762
|
-
}
|
|
42063
|
+
visited.add(range);
|
|
42064
|
+
const impactedRanges = this.rTree.search(range);
|
|
42065
|
+
queue.push(...impactedRanges.difference(visited));
|
|
41763
42066
|
}
|
|
41764
42067
|
// remove initial ranges
|
|
41765
42068
|
for (const range of ranges) {
|
|
41766
|
-
|
|
41767
|
-
const sheetId = range.sheetId;
|
|
41768
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
41769
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41770
|
-
visited.delete({ sheetId, col, row });
|
|
41771
|
-
}
|
|
41772
|
-
}
|
|
42069
|
+
visited.delete(range);
|
|
41773
42070
|
}
|
|
41774
42071
|
return visited;
|
|
41775
42072
|
}
|
|
@@ -42046,7 +42343,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42046
42343
|
getters;
|
|
42047
42344
|
compilationParams;
|
|
42048
42345
|
evaluatedCells = new PositionMap();
|
|
42049
|
-
formulaDependencies = lazy(new FormulaDependencyGraph(
|
|
42346
|
+
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
42050
42347
|
blockedArrayFormulas = new PositionSet({});
|
|
42051
42348
|
spreadingRelations = new SpreadingRelation();
|
|
42052
42349
|
constructor(context, getters) {
|
|
@@ -42081,7 +42378,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42081
42378
|
return undefined;
|
|
42082
42379
|
}
|
|
42083
42380
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
42084
|
-
return
|
|
42381
|
+
return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
|
|
42085
42382
|
}
|
|
42086
42383
|
updateDependencies(position) {
|
|
42087
42384
|
// removing dependencies is slow because it requires
|
|
@@ -42125,57 +42422,72 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42125
42422
|
}
|
|
42126
42423
|
evaluateCells(positions) {
|
|
42127
42424
|
const start = performance.now();
|
|
42128
|
-
const
|
|
42129
|
-
|
|
42425
|
+
const rangesToCompute = new RangeSet();
|
|
42426
|
+
rangesToCompute.addManyPositions(positions);
|
|
42130
42427
|
const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
|
|
42131
|
-
|
|
42132
|
-
|
|
42133
|
-
|
|
42134
|
-
this.evaluate(
|
|
42428
|
+
rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
|
|
42429
|
+
rangesToCompute.addMany(arrayFormulasPositions);
|
|
42430
|
+
rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
42431
|
+
this.evaluate(rangesToCompute);
|
|
42135
42432
|
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
42136
42433
|
}
|
|
42137
42434
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
42138
|
-
const
|
|
42435
|
+
const impactedRanges = new RangeSet();
|
|
42139
42436
|
for (const position of positions) {
|
|
42140
42437
|
const content = this.getters.getCell(position)?.content;
|
|
42141
42438
|
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
42142
42439
|
if (arrayFormulaPosition !== undefined) {
|
|
42143
42440
|
// take into account new collisions.
|
|
42144
|
-
|
|
42441
|
+
impactedRanges.addPosition(arrayFormulaPosition);
|
|
42145
42442
|
}
|
|
42146
42443
|
if (!content) {
|
|
42147
42444
|
// The previous content could have blocked some array formulas
|
|
42148
|
-
|
|
42445
|
+
impactedRanges.addPosition(position);
|
|
42149
42446
|
}
|
|
42150
42447
|
}
|
|
42151
|
-
const
|
|
42152
|
-
|
|
42153
|
-
for (const zone of zonesBySheetIds[sheetId]) {
|
|
42154
|
-
impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
42155
|
-
}
|
|
42448
|
+
for (const range of [...impactedRanges]) {
|
|
42449
|
+
impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
|
|
42156
42450
|
}
|
|
42157
|
-
return
|
|
42451
|
+
return impactedRanges;
|
|
42158
42452
|
}
|
|
42159
42453
|
buildDependencyGraph() {
|
|
42160
42454
|
this.blockedArrayFormulas = this.createEmptyPositionSet();
|
|
42161
42455
|
this.spreadingRelations = new SpreadingRelation();
|
|
42162
42456
|
this.formulaDependencies = lazy(() => {
|
|
42163
|
-
const
|
|
42164
|
-
|
|
42165
|
-
.
|
|
42166
|
-
|
|
42167
|
-
|
|
42168
|
-
|
|
42169
|
-
|
|
42170
|
-
|
|
42171
|
-
|
|
42172
|
-
|
|
42457
|
+
const rTreeItems = [];
|
|
42458
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
42459
|
+
const cells = this.getters.getCells(sheetId);
|
|
42460
|
+
for (const cellId in cells) {
|
|
42461
|
+
const cell = cells[cellId];
|
|
42462
|
+
if (cell.isFormula) {
|
|
42463
|
+
const directDependencies = cell.compiledFormula.dependencies;
|
|
42464
|
+
for (const range of directDependencies) {
|
|
42465
|
+
if (range.invalidSheetName || range.invalidXc) {
|
|
42466
|
+
continue;
|
|
42467
|
+
}
|
|
42468
|
+
rTreeItems.push({
|
|
42469
|
+
data: {
|
|
42470
|
+
sheetId,
|
|
42471
|
+
zone: positionToZone(this.getters.getCellPosition(cellId)),
|
|
42472
|
+
},
|
|
42473
|
+
boundingBox: { sheetId: range.sheetId, zone: range.zone },
|
|
42474
|
+
});
|
|
42475
|
+
}
|
|
42476
|
+
}
|
|
42477
|
+
}
|
|
42478
|
+
}
|
|
42479
|
+
return new FormulaDependencyGraph(rTreeItems);
|
|
42173
42480
|
});
|
|
42174
42481
|
}
|
|
42175
42482
|
evaluateAllCells() {
|
|
42176
42483
|
const start = performance.now();
|
|
42177
42484
|
this.evaluatedCells = new PositionMap();
|
|
42178
|
-
|
|
42485
|
+
const ranges = [];
|
|
42486
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
42487
|
+
const zone = this.getters.getSheetZone(sheetId);
|
|
42488
|
+
ranges.push({ sheetId, zone });
|
|
42489
|
+
}
|
|
42490
|
+
this.evaluate(ranges);
|
|
42179
42491
|
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
42180
42492
|
}
|
|
42181
42493
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
@@ -42199,48 +42511,47 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42199
42511
|
return handleError(error, "");
|
|
42200
42512
|
}
|
|
42201
42513
|
}
|
|
42202
|
-
getAllCells() {
|
|
42203
|
-
const positions = this.createEmptyPositionSet();
|
|
42204
|
-
positions.fillAllPositions();
|
|
42205
|
-
return positions;
|
|
42206
|
-
}
|
|
42207
42514
|
/**
|
|
42208
42515
|
* Return the position of formulas blocked by the given positions
|
|
42209
42516
|
* as well as all their dependencies.
|
|
42210
42517
|
*/
|
|
42211
42518
|
getArrayFormulasBlockedBy(sheetId, zone) {
|
|
42212
|
-
const arrayFormulaPositions =
|
|
42519
|
+
const arrayFormulaPositions = new RangeSet();
|
|
42213
42520
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
|
|
42214
|
-
arrayFormulaPositions.
|
|
42521
|
+
arrayFormulaPositions.addManyPositions(arrayFormulas);
|
|
42215
42522
|
const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
|
|
42216
42523
|
if (spilledPositions.length) {
|
|
42217
42524
|
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
42218
|
-
arrayFormulaPositions.
|
|
42525
|
+
arrayFormulaPositions.deleteManyPositions(spilledPositions);
|
|
42219
42526
|
}
|
|
42220
42527
|
arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
|
|
42221
42528
|
return arrayFormulaPositions;
|
|
42222
42529
|
}
|
|
42223
|
-
|
|
42530
|
+
nextRangesToUpdate = new RangeSet();
|
|
42224
42531
|
cellsBeingComputed = new Set();
|
|
42225
42532
|
symbolsBeingComputed = new Set();
|
|
42226
|
-
evaluate(
|
|
42533
|
+
evaluate(ranges) {
|
|
42227
42534
|
this.cellsBeingComputed = new Set();
|
|
42228
|
-
this.
|
|
42535
|
+
this.nextRangesToUpdate = new RangeSet(ranges);
|
|
42229
42536
|
let currentIteration = 0;
|
|
42230
|
-
while (!this.
|
|
42537
|
+
while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
|
|
42231
42538
|
this.updateCompilationParameters();
|
|
42232
|
-
const
|
|
42233
|
-
|
|
42234
|
-
|
|
42235
|
-
|
|
42236
|
-
|
|
42237
|
-
|
|
42238
|
-
|
|
42239
|
-
|
|
42240
|
-
|
|
42241
|
-
|
|
42242
|
-
|
|
42243
|
-
|
|
42539
|
+
const ranges = [...this.nextRangesToUpdate];
|
|
42540
|
+
this.nextRangesToUpdate.clear();
|
|
42541
|
+
this.clearEvaluatedRanges(ranges);
|
|
42542
|
+
for (const range of ranges) {
|
|
42543
|
+
const { left, bottom, right, top } = range.zone;
|
|
42544
|
+
for (let col = left; col <= right; col++) {
|
|
42545
|
+
for (let row = top; row <= bottom; row++) {
|
|
42546
|
+
const position = { sheetId: range.sheetId, col, row };
|
|
42547
|
+
if (this.nextRangesToUpdate.hasPosition(position)) {
|
|
42548
|
+
continue;
|
|
42549
|
+
}
|
|
42550
|
+
const evaluatedCell = this.computeCell(position);
|
|
42551
|
+
if (evaluatedCell !== EMPTY_CELL) {
|
|
42552
|
+
this.evaluatedCells.set(position, evaluatedCell);
|
|
42553
|
+
}
|
|
42554
|
+
}
|
|
42244
42555
|
}
|
|
42245
42556
|
}
|
|
42246
42557
|
onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
|
|
@@ -42249,6 +42560,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42249
42560
|
console.warn("Maximum iteration reached while evaluating cells");
|
|
42250
42561
|
}
|
|
42251
42562
|
}
|
|
42563
|
+
clearEvaluatedRanges(ranges) {
|
|
42564
|
+
for (const range of ranges) {
|
|
42565
|
+
const { left, bottom, right, top } = range.zone;
|
|
42566
|
+
for (let col = left; col <= right; col++) {
|
|
42567
|
+
for (let row = top; row <= bottom; row++) {
|
|
42568
|
+
this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
|
|
42569
|
+
}
|
|
42570
|
+
}
|
|
42571
|
+
}
|
|
42572
|
+
}
|
|
42252
42573
|
computeCell(position) {
|
|
42253
42574
|
const evaluation = this.evaluatedCells.get(position);
|
|
42254
42575
|
if (evaluation) {
|
|
@@ -42321,9 +42642,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42321
42642
|
}
|
|
42322
42643
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
42323
42644
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
42324
|
-
const invalidatedPositions = this.
|
|
42325
|
-
invalidatedPositions.delete({ sheetId,
|
|
42326
|
-
this.
|
|
42645
|
+
const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
42646
|
+
invalidatedPositions.delete({ sheetId, zone: resultZone });
|
|
42647
|
+
this.nextRangesToUpdate.addMany(invalidatedPositions);
|
|
42327
42648
|
}
|
|
42328
42649
|
assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
|
|
42329
42650
|
const numberOfCols = this.getters.getNumberCols(sheetId);
|
|
@@ -42398,7 +42719,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42398
42719
|
}
|
|
42399
42720
|
const sheetId = position.sheetId;
|
|
42400
42721
|
this.invalidatePositionsDependingOnSpread(sheetId, zone);
|
|
42401
|
-
this.
|
|
42722
|
+
this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
42402
42723
|
}
|
|
42403
42724
|
/**
|
|
42404
42725
|
* Wraps a GetSymbolValue function to add cycle detection
|
|
@@ -42433,13 +42754,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42433
42754
|
}
|
|
42434
42755
|
return cell.compiledFormula.dependencies;
|
|
42435
42756
|
}
|
|
42436
|
-
getCellsDependingOn(
|
|
42437
|
-
|
|
42438
|
-
const zonesBySheetIds = aggregatePositionsToZones(positions);
|
|
42439
|
-
for (const sheetId in zonesBySheetIds) {
|
|
42440
|
-
ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
|
|
42441
|
-
}
|
|
42442
|
-
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
|
|
42757
|
+
getCellsDependingOn(ranges) {
|
|
42758
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
|
|
42443
42759
|
}
|
|
42444
42760
|
}
|
|
42445
42761
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -46106,18 +46422,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46106
46422
|
return EMPTY_PIVOT_CELL;
|
|
46107
46423
|
}
|
|
46108
46424
|
if (functionName === "PIVOT") {
|
|
46109
|
-
const
|
|
46110
|
-
const
|
|
46111
|
-
const includeColumnHeaders = toScalar(args[3]);
|
|
46112
|
-
const includeMeasures = toScalar(args[5]);
|
|
46113
|
-
const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
|
|
46114
|
-
const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
|
|
46115
|
-
const visibilityOptions = {
|
|
46116
|
-
displayColumnHeaders: shouldIncludeColumnHeaders,
|
|
46117
|
-
displayTotals: shouldIncludeTotal,
|
|
46118
|
-
displayMeasuresRow: shouldIncludeMeasures,
|
|
46119
|
-
};
|
|
46120
|
-
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
|
|
46425
|
+
const pivotStyle = getPivotStyleFromFnArgs(this.getters.getPivotCoreDefinition(pivotId), toScalar(args[1]), toScalar(args[2]), toScalar(args[3]), toScalar(args[4]), toScalar(args[5]), this.getters.getLocale());
|
|
46426
|
+
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
|
|
46121
46427
|
const pivotCol = position.col - mainPosition.col;
|
|
46122
46428
|
const pivotRow = position.row - mainPosition.row;
|
|
46123
46429
|
return pivotCells[pivotCol][pivotRow];
|
|
@@ -55832,7 +56138,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
55832
56138
|
const startSnapshot = performance.now();
|
|
55833
56139
|
console.debug("Snapshot requested");
|
|
55834
56140
|
this.session.snapshot(this.exportData());
|
|
55835
|
-
this.garbageCollectExternalResources();
|
|
55836
56141
|
console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
|
|
55837
56142
|
}
|
|
55838
56143
|
console.debug("Model created in", performance.now() - start, "ms");
|
|
@@ -56218,11 +56523,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56218
56523
|
data = deepCopy$1(data);
|
|
56219
56524
|
return getXLSX(data);
|
|
56220
56525
|
}
|
|
56221
|
-
garbageCollectExternalResources() {
|
|
56222
|
-
for (const plugin of this.corePlugins) {
|
|
56223
|
-
plugin.garbageCollectExternalResources();
|
|
56224
|
-
}
|
|
56225
|
-
}
|
|
56226
56526
|
}
|
|
56227
56527
|
function createCommand(type, payload = {}) {
|
|
56228
56528
|
const command = deepCopy$1(payload);
|
|
@@ -65613,12 +65913,23 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65613
65913
|
.add("LinkEditor", LinkEditorPopoverBuilder)
|
|
65614
65914
|
.add("FilterMenu", FilterMenuPopoverBuilder);
|
|
65615
65915
|
|
|
65616
|
-
const
|
|
65617
|
-
|
|
65618
|
-
|
|
65619
|
-
|
|
65620
|
-
|
|
65621
|
-
|
|
65916
|
+
const DEFAULT_BAR_CHART_CONFIG = {
|
|
65917
|
+
type: "bar",
|
|
65918
|
+
title: {},
|
|
65919
|
+
dataSets: [],
|
|
65920
|
+
legendPosition: "none",
|
|
65921
|
+
dataSetsHaveTitle: false,
|
|
65922
|
+
stacked: false,
|
|
65923
|
+
};
|
|
65924
|
+
const DEFAULT_LINE_CHART_CONFIG = {
|
|
65925
|
+
type: "line",
|
|
65926
|
+
title: {},
|
|
65927
|
+
dataSets: [],
|
|
65928
|
+
legendPosition: "none",
|
|
65929
|
+
dataSetsHaveTitle: false,
|
|
65930
|
+
stacked: false,
|
|
65931
|
+
cumulative: false,
|
|
65932
|
+
labelsAsText: false,
|
|
65622
65933
|
};
|
|
65623
65934
|
function getUnboundRange(getters, zone) {
|
|
65624
65935
|
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
@@ -65657,43 +65968,19 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65657
65968
|
return detectedType;
|
|
65658
65969
|
}
|
|
65659
65970
|
function categorizeColumns(zones, getters) {
|
|
65660
|
-
const columns =
|
|
65661
|
-
number: [],
|
|
65662
|
-
text: [],
|
|
65663
|
-
date: [],
|
|
65664
|
-
};
|
|
65971
|
+
const columns = [];
|
|
65665
65972
|
for (const zone of getZonesByColumns(zones)) {
|
|
65666
65973
|
const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
|
|
65667
|
-
|
|
65668
|
-
if (type !== "empty") {
|
|
65669
|
-
const targetType = type === "percentage" ? "number" : type;
|
|
65670
|
-
columns[targetType].push({ zone, type });
|
|
65671
|
-
}
|
|
65974
|
+
columns.push({ zone, type: detectColumnType(cells) });
|
|
65672
65975
|
}
|
|
65673
65976
|
return columns;
|
|
65674
65977
|
}
|
|
65675
65978
|
function getCellStats(getters, zone) {
|
|
65676
65979
|
const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
|
|
65677
|
-
const
|
|
65678
|
-
let totalCount = 0;
|
|
65679
|
-
let percentageSum = 0;
|
|
65680
|
-
for (let i = 0; i < cells.length; i++) {
|
|
65681
|
-
const { value } = cells[i];
|
|
65682
|
-
const str = value?.toString().trim();
|
|
65683
|
-
if (!str) {
|
|
65684
|
-
continue;
|
|
65685
|
-
}
|
|
65686
|
-
uniqueValues.add(str);
|
|
65687
|
-
totalCount++;
|
|
65688
|
-
const num = Number(value);
|
|
65689
|
-
if (!isNaN(num)) {
|
|
65690
|
-
percentageSum += Math.abs(num) * 100;
|
|
65691
|
-
}
|
|
65692
|
-
}
|
|
65980
|
+
const values = cells.map((c) => c.value?.toString().trim() || "").filter((s) => s);
|
|
65693
65981
|
return {
|
|
65694
|
-
uniqueCount:
|
|
65695
|
-
totalCount,
|
|
65696
|
-
percentageSum,
|
|
65982
|
+
uniqueCount: new Set(values).size,
|
|
65983
|
+
totalCount: values.length,
|
|
65697
65984
|
};
|
|
65698
65985
|
}
|
|
65699
65986
|
function isDatasetTitled(getters, column) {
|
|
@@ -65704,167 +65991,191 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65704
65991
|
});
|
|
65705
65992
|
return ![CellValueType.number, CellValueType.empty].includes(titleCell.type);
|
|
65706
65993
|
}
|
|
65707
|
-
|
|
65708
|
-
|
|
65709
|
-
|
|
65710
|
-
|
|
65711
|
-
|
|
65712
|
-
|
|
65713
|
-
|
|
65714
|
-
|
|
65715
|
-
}
|
|
65994
|
+
/**
|
|
65995
|
+
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
65996
|
+
* - If the column contains a single cell, create a scorecard.
|
|
65997
|
+
* - If the column type is "percentage", create a pie chart.
|
|
65998
|
+
* - If the column type is "text", create a pie chart
|
|
65999
|
+
* - If the column type is "date", create a line chart.
|
|
66000
|
+
* - Otherwise, create a bar chart.
|
|
66001
|
+
*/
|
|
65716
66002
|
function buildSingleColumnChart(column, getters) {
|
|
65717
66003
|
const { type, zone } = column;
|
|
65718
66004
|
const sheetId = getters.getActiveSheetId();
|
|
65719
66005
|
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
65720
66006
|
const dataRange = getUnboundRange(getters, zone);
|
|
65721
66007
|
const titleCell = getters.getEvaluatedCell({ sheetId, col: zone.left, row: zone.top });
|
|
66008
|
+
if (getZoneArea(zone) === 1) {
|
|
66009
|
+
return buildScorecard(zone, getters);
|
|
66010
|
+
}
|
|
65722
66011
|
switch (type) {
|
|
65723
66012
|
case "percentage":
|
|
65724
|
-
|
|
65725
|
-
|
|
66013
|
+
return {
|
|
66014
|
+
type: "pie",
|
|
65726
66015
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66016
|
+
dataSets: [{ dataRange }],
|
|
66017
|
+
legendPosition: "none",
|
|
65727
66018
|
dataSetsHaveTitle,
|
|
65728
|
-
|
|
65729
|
-
});
|
|
66019
|
+
};
|
|
65730
66020
|
case "text":
|
|
65731
66021
|
const cells = getters.getEvaluatedCellsInZone(sheetId, zone);
|
|
65732
66022
|
const titleCount = cells.reduce((count, cell) => (cell.value === titleCell.value ? count + 1 : count), 0);
|
|
65733
66023
|
const hasUniqueTitle = titleCell.value !== null && titleCount === 1;
|
|
65734
|
-
return
|
|
66024
|
+
return {
|
|
66025
|
+
type: "pie",
|
|
65735
66026
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
66027
|
+
dataSets: [{ dataRange }],
|
|
65736
66028
|
labelRange: dataRange,
|
|
65737
66029
|
dataSetsHaveTitle: hasUniqueTitle,
|
|
65738
|
-
isDoughnut: false,
|
|
65739
66030
|
aggregated: true,
|
|
65740
66031
|
legendPosition: "top",
|
|
65741
|
-
}
|
|
65742
|
-
// TODO: Handle date column with matrix chart when matrix chart is supported
|
|
66032
|
+
};
|
|
65743
66033
|
case "date":
|
|
65744
|
-
return
|
|
65745
|
-
|
|
66034
|
+
return {
|
|
66035
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66036
|
+
type: "line",
|
|
66037
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66038
|
+
dataSets: [{ dataRange }],
|
|
65746
66039
|
dataSetsHaveTitle,
|
|
65747
|
-
|
|
65748
|
-
labelsAsText: false,
|
|
65749
|
-
});
|
|
66040
|
+
};
|
|
65750
66041
|
}
|
|
65751
|
-
return
|
|
66042
|
+
return {
|
|
66043
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66044
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66045
|
+
dataSets: [{ dataRange }],
|
|
66046
|
+
dataSetsHaveTitle,
|
|
66047
|
+
};
|
|
65752
66048
|
}
|
|
66049
|
+
/**
|
|
66050
|
+
* Builds a chart definition for a selection of two columns. The logic to detect the chart type always consider the
|
|
66051
|
+
* columns left to right, and is as follows:
|
|
66052
|
+
* - any type + percentage columns: pie chart
|
|
66053
|
+
* - number + number columns: scatter chart
|
|
66054
|
+
* - date + number columns: line chart
|
|
66055
|
+
* - text + number columns: treemap if repetition in labels
|
|
66056
|
+
* - any other combination: bar chart
|
|
66057
|
+
*/
|
|
65753
66058
|
function buildTwoColumnChart(columns, getters) {
|
|
65754
|
-
|
|
65755
|
-
|
|
65756
|
-
return createBaseChart("scatter", [{ dataRange: getUnboundRange(getters, numberColumns[1].zone) }], {
|
|
65757
|
-
labelRange: getUnboundRange(getters, numberColumns[0].zone),
|
|
65758
|
-
dataSetsHaveTitle: isDatasetTitled(getters, numberColumns[1]),
|
|
65759
|
-
labelsAsText: false,
|
|
65760
|
-
});
|
|
66059
|
+
if (columns.length !== 2) {
|
|
66060
|
+
throw new Error("buildTwoColumnChart expects exactly two columns");
|
|
65761
66061
|
}
|
|
65762
|
-
|
|
65763
|
-
|
|
65764
|
-
|
|
65765
|
-
|
|
65766
|
-
|
|
65767
|
-
|
|
65768
|
-
|
|
66062
|
+
if (columns[1].type === "percentage") {
|
|
66063
|
+
return {
|
|
66064
|
+
type: "pie",
|
|
66065
|
+
title: {},
|
|
66066
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66067
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66068
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
66069
|
+
aggregated: true,
|
|
66070
|
+
legendPosition: "none",
|
|
66071
|
+
};
|
|
66072
|
+
}
|
|
66073
|
+
if (columns[0].type === "number" && columns[1].type === "number") {
|
|
66074
|
+
return {
|
|
66075
|
+
type: "scatter",
|
|
66076
|
+
title: {},
|
|
66077
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66078
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66079
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
65769
66080
|
labelsAsText: false,
|
|
65770
|
-
|
|
66081
|
+
legendPosition: "none",
|
|
66082
|
+
};
|
|
65771
66083
|
}
|
|
65772
|
-
|
|
65773
|
-
|
|
65774
|
-
|
|
66084
|
+
// TODO: Handle date + number with calendar chart when implemented (and change the docstring)
|
|
66085
|
+
if (columns[0].type === "date" && columns[1].type === "number") {
|
|
66086
|
+
return {
|
|
66087
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66088
|
+
type: "line",
|
|
66089
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66090
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66091
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[0]),
|
|
66092
|
+
};
|
|
66093
|
+
}
|
|
66094
|
+
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
66095
|
+
const textColumn = columns[0];
|
|
66096
|
+
const numberColumn = columns[1];
|
|
65775
66097
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
65776
66098
|
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
65777
|
-
const maxCategories = dataSetsHaveTitle
|
|
65778
|
-
? CHART_LIMITS.MAX_PIE_CATEGORIES
|
|
65779
|
-
: CHART_LIMITS.MAX_PIE_CATEGORIES_NO_TITLE;
|
|
65780
|
-
const labelRange = getUnboundRange(getters, textColumn.zone);
|
|
65781
|
-
const dataRange = getUnboundRange(getters, numberColumn.zone);
|
|
65782
|
-
if (uniqueCount <= maxCategories) {
|
|
65783
|
-
const { percentageSum } = getCellStats(getters, numberColumn.zone);
|
|
65784
|
-
return createBaseChart("pie", [{ dataRange }], {
|
|
65785
|
-
labelRange,
|
|
65786
|
-
dataSetsHaveTitle,
|
|
65787
|
-
isDoughnut: numberColumn.type === "percentage" && percentageSum < CHART_LIMITS.PERCENTAGE_THRESHOLD,
|
|
65788
|
-
aggregated: true,
|
|
65789
|
-
legendPosition: "top",
|
|
65790
|
-
});
|
|
65791
|
-
}
|
|
65792
|
-
// Use treemap when categories repeat, as pie chart would be cluttered
|
|
65793
66099
|
if (uniqueCount !== totalCount) {
|
|
65794
|
-
return
|
|
65795
|
-
|
|
66100
|
+
return {
|
|
66101
|
+
type: "treemap",
|
|
66102
|
+
title: {},
|
|
66103
|
+
dataSets: [{ dataRange: getUnboundRange(getters, textColumn.zone) }],
|
|
66104
|
+
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
65796
66105
|
dataSetsHaveTitle,
|
|
65797
|
-
|
|
66106
|
+
legendPosition: "none",
|
|
66107
|
+
};
|
|
65798
66108
|
}
|
|
65799
|
-
return createBaseChart("bar", [{ dataRange }], {
|
|
65800
|
-
labelRange,
|
|
65801
|
-
dataSetsHaveTitle,
|
|
65802
|
-
});
|
|
65803
66109
|
}
|
|
65804
|
-
|
|
65805
|
-
|
|
65806
|
-
|
|
65807
|
-
labelRange: getUnboundRange(getters,
|
|
65808
|
-
dataSetsHaveTitle: isDatasetTitled(getters,
|
|
65809
|
-
|
|
65810
|
-
labelsAsText: true,
|
|
65811
|
-
});
|
|
66110
|
+
return {
|
|
66111
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66112
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66113
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66114
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
66115
|
+
};
|
|
65812
66116
|
}
|
|
66117
|
+
/**
|
|
66118
|
+
* Builds a chart definition for a selection more than two columns. The logic to detect the chart type always consider
|
|
66119
|
+
* the columns left to right, and is as follows:
|
|
66120
|
+
* - multiple text + single number/percentage columns: sunburst if 3+ text columns, treemap otherwise
|
|
66121
|
+
* - any type + multiple percentage columns: pie chart
|
|
66122
|
+
* - date + multiple number columns: line chart
|
|
66123
|
+
* - any other combination: bar chart
|
|
66124
|
+
*/
|
|
65813
66125
|
function buildMultiColumnChart(columns, getters) {
|
|
65814
|
-
|
|
65815
|
-
|
|
65816
|
-
|
|
65817
|
-
|
|
65818
|
-
|
|
66126
|
+
if (columns.length < 3) {
|
|
66127
|
+
throw new Error("buildMultiColumnChart expects at least three columns");
|
|
66128
|
+
}
|
|
66129
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
66130
|
+
const lastColumn = columns[columns.length - 1];
|
|
66131
|
+
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
66132
|
+
if ((lastColumn.type === "percentage" || lastColumn.type === "number") &&
|
|
66133
|
+
columnsExceptLast.every((col) => col.type === "text")) {
|
|
66134
|
+
const dataSets = columnsExceptLast.map(({ zone }) => ({
|
|
65819
66135
|
dataRange: getUnboundRange(getters, zone),
|
|
65820
66136
|
}));
|
|
65821
|
-
return
|
|
65822
|
-
|
|
66137
|
+
return {
|
|
66138
|
+
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
66139
|
+
title: {},
|
|
66140
|
+
dataSets,
|
|
66141
|
+
labelRange: getUnboundRange(getters, lastColumn.zone),
|
|
65823
66142
|
dataSetsHaveTitle,
|
|
65824
|
-
|
|
66143
|
+
legendPosition: "none",
|
|
66144
|
+
};
|
|
65825
66145
|
}
|
|
65826
|
-
const
|
|
66146
|
+
const firstColumn = columns[0];
|
|
66147
|
+
const columnsExceptFirst = columns.slice(1);
|
|
66148
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }) => ({
|
|
65827
66149
|
dataRange: getUnboundRange(getters, zone),
|
|
65828
66150
|
}));
|
|
65829
|
-
if (
|
|
65830
|
-
return
|
|
65831
|
-
|
|
66151
|
+
if (columnsExceptFirst.every((col) => col.type === "percentage")) {
|
|
66152
|
+
return {
|
|
66153
|
+
type: "pie",
|
|
66154
|
+
title: {},
|
|
66155
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66156
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
65832
66157
|
dataSetsHaveTitle,
|
|
65833
|
-
|
|
65834
|
-
labelsAsText: false,
|
|
66158
|
+
aggregated: false,
|
|
65835
66159
|
legendPosition: "top",
|
|
65836
|
-
}
|
|
66160
|
+
};
|
|
65837
66161
|
}
|
|
65838
|
-
if (
|
|
65839
|
-
|
|
65840
|
-
|
|
65841
|
-
|
|
65842
|
-
|
|
65843
|
-
|
|
65844
|
-
|
|
65845
|
-
|
|
65846
|
-
|
|
65847
|
-
const expectedDataCount = categoryCount * numberColumns.length + (dataSetsHaveTitle ? numberColumns.length : 0);
|
|
65848
|
-
const actualDataCount = numberColumns.reduce((sum, dataCol) => sum + getCellStats(getters, dataCol.zone).totalCount, 0);
|
|
65849
|
-
if (uniqueCount === totalCount &&
|
|
65850
|
-
uniqueCount >= CHART_LIMITS.MIN_RADAR_CATEGORIES &&
|
|
65851
|
-
uniqueCount <= CHART_LIMITS.MAX_RADAR_CATEGORIES &&
|
|
65852
|
-
expectedDataCount === actualDataCount) {
|
|
65853
|
-
return createBaseChart("radar", dataSets, {
|
|
65854
|
-
title: dataSetsHaveTitle && firstCell.value ? { text: String(firstCell.value) } : {},
|
|
65855
|
-
labelRange: getUnboundRange(getters, textColumn.zone),
|
|
65856
|
-
dataSetsHaveTitle,
|
|
65857
|
-
legendPosition: "top",
|
|
65858
|
-
});
|
|
65859
|
-
}
|
|
66162
|
+
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) {
|
|
66163
|
+
return {
|
|
66164
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66165
|
+
type: "line",
|
|
66166
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66167
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
66168
|
+
dataSetsHaveTitle,
|
|
66169
|
+
legendPosition: "top",
|
|
66170
|
+
};
|
|
65860
66171
|
}
|
|
65861
|
-
|
|
65862
|
-
|
|
65863
|
-
|
|
66172
|
+
return {
|
|
66173
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66174
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66175
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
65864
66176
|
dataSetsHaveTitle,
|
|
65865
|
-
aggregated: true,
|
|
65866
66177
|
legendPosition: "top",
|
|
65867
|
-
}
|
|
66178
|
+
};
|
|
65868
66179
|
}
|
|
65869
66180
|
function buildScorecard(zone, getters) {
|
|
65870
66181
|
const cell = getters.getCell({
|
|
@@ -65887,22 +66198,18 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65887
66198
|
*/
|
|
65888
66199
|
function getSmartChartDefinition(zones, getters) {
|
|
65889
66200
|
const columns = categorizeColumns(zones, getters);
|
|
65890
|
-
|
|
65891
|
-
|
|
65892
|
-
|
|
65893
|
-
|
|
65894
|
-
|
|
65895
|
-
|
|
65896
|
-
});
|
|
66201
|
+
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
66202
|
+
const dataSets = columns.map(({ zone }) => ({ dataRange: getUnboundRange(getters, zone) }));
|
|
66203
|
+
return { ...DEFAULT_BAR_CHART_CONFIG, dataSets };
|
|
66204
|
+
}
|
|
66205
|
+
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
66206
|
+
switch (nonEmptyColumns.length) {
|
|
65897
66207
|
case 1:
|
|
65898
|
-
|
|
65899
|
-
return getZoneArea(singleColumn.zone) === 1
|
|
65900
|
-
? buildScorecard(singleColumn.zone, getters)
|
|
65901
|
-
: buildSingleColumnChart(singleColumn, getters);
|
|
66208
|
+
return buildSingleColumnChart(nonEmptyColumns[0], getters);
|
|
65902
66209
|
case 2:
|
|
65903
|
-
return buildTwoColumnChart(
|
|
66210
|
+
return buildTwoColumnChart(nonEmptyColumns, getters);
|
|
65904
66211
|
default:
|
|
65905
|
-
return buildMultiColumnChart(
|
|
66212
|
+
return buildMultiColumnChart(nonEmptyColumns, getters);
|
|
65906
66213
|
}
|
|
65907
66214
|
}
|
|
65908
66215
|
|
|
@@ -66435,23 +66742,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
66435
66742
|
//------------------------------------------------------------------------------
|
|
66436
66743
|
// Image
|
|
66437
66744
|
//------------------------------------------------------------------------------
|
|
66438
|
-
async function requestImage(env) {
|
|
66439
|
-
try {
|
|
66440
|
-
return await env.imageProvider.requestImage();
|
|
66441
|
-
}
|
|
66442
|
-
catch {
|
|
66443
|
-
env.raiseError(_t$1("An unexpected error occurred during the image transfer"));
|
|
66444
|
-
return;
|
|
66445
|
-
}
|
|
66446
|
-
}
|
|
66447
66745
|
const CREATE_IMAGE = async (env) => {
|
|
66448
66746
|
if (env.imageProvider) {
|
|
66449
66747
|
const sheetId = env.model.getters.getActiveSheetId();
|
|
66450
66748
|
const figureId = env.model.uuidGenerator.smallUuid();
|
|
66451
|
-
const image = await requestImage(
|
|
66452
|
-
if (!image) {
|
|
66453
|
-
return;
|
|
66454
|
-
}
|
|
66749
|
+
const image = await env.imageProvider.requestImage();
|
|
66455
66750
|
const size = getMaxFigureSize(env.model.getters, image.size);
|
|
66456
66751
|
const { col, row, offset } = centerFigurePosition(env.model.getters, size);
|
|
66457
66752
|
env.model.dispatch("CREATE_IMAGE", {
|
|
@@ -72839,39 +73134,38 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72839
73134
|
}, () => [ref.el]);
|
|
72840
73135
|
}
|
|
72841
73136
|
|
|
72842
|
-
class
|
|
72843
|
-
static template = "o-spreadsheet-TextInput";
|
|
73137
|
+
class GenericInput extends owl.Component {
|
|
72844
73138
|
static props = {
|
|
72845
|
-
value: String,
|
|
73139
|
+
value: [Number, String],
|
|
72846
73140
|
onChange: Function,
|
|
72847
|
-
class: {
|
|
72848
|
-
|
|
72849
|
-
|
|
72850
|
-
},
|
|
72851
|
-
id: {
|
|
72852
|
-
type: String,
|
|
72853
|
-
optional: true,
|
|
72854
|
-
},
|
|
72855
|
-
placeholder: {
|
|
72856
|
-
type: String,
|
|
72857
|
-
optional: true,
|
|
72858
|
-
},
|
|
72859
|
-
autofocus: {
|
|
72860
|
-
type: Boolean,
|
|
72861
|
-
optional: true,
|
|
72862
|
-
},
|
|
73141
|
+
class: { type: String, optional: true },
|
|
73142
|
+
id: { type: String, optional: true },
|
|
73143
|
+
placeholder: { type: String, optional: true },
|
|
73144
|
+
autofocus: { type: Boolean, optional: true },
|
|
72863
73145
|
alwaysShowBorder: { type: Boolean, optional: true },
|
|
73146
|
+
selectContentOnFocus: { type: Boolean, optional: true },
|
|
72864
73147
|
};
|
|
72865
|
-
|
|
73148
|
+
refName = "input";
|
|
73149
|
+
inputRef;
|
|
72866
73150
|
setup() {
|
|
73151
|
+
this.inputRef = owl.useRef(this.refName);
|
|
72867
73152
|
owl.useExternalListener(window, "click", (ev) => {
|
|
72868
73153
|
if (ev.target !== this.inputRef.el && this.inputRef.el?.value !== this.props.value) {
|
|
72869
73154
|
this.save();
|
|
72870
73155
|
}
|
|
72871
73156
|
}, { capture: true });
|
|
72872
73157
|
if (this.props.autofocus) {
|
|
72873
|
-
useAutofocus({ refName:
|
|
73158
|
+
useAutofocus({ refName: this.refName });
|
|
72874
73159
|
}
|
|
73160
|
+
owl.onWillUpdateProps((nextProps) => {
|
|
73161
|
+
if (document.activeElement !== this.inputRef.el && this.inputRef.el) {
|
|
73162
|
+
this.inputRef.el.value = nextProps.value;
|
|
73163
|
+
}
|
|
73164
|
+
});
|
|
73165
|
+
owl.onMounted(() => {
|
|
73166
|
+
if (this.inputRef.el)
|
|
73167
|
+
this.inputRef.el.value = this.props.value.toString();
|
|
73168
|
+
});
|
|
72875
73169
|
}
|
|
72876
73170
|
onKeyDown(ev) {
|
|
72877
73171
|
switch (ev.key) {
|
|
@@ -72882,7 +73176,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72882
73176
|
break;
|
|
72883
73177
|
case "Escape":
|
|
72884
73178
|
if (this.inputRef.el) {
|
|
72885
|
-
this.inputRef.el.value = this.props.value;
|
|
73179
|
+
this.inputRef.el.value = this.props.value.toString();
|
|
72886
73180
|
this.inputRef.el.blur();
|
|
72887
73181
|
}
|
|
72888
73182
|
ev.preventDefault();
|
|
@@ -72890,12 +73184,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72890
73184
|
break;
|
|
72891
73185
|
}
|
|
72892
73186
|
}
|
|
72893
|
-
save() {
|
|
73187
|
+
save(keepFocus = false) {
|
|
72894
73188
|
const currentValue = (this.inputRef.el?.value || "").trim();
|
|
72895
|
-
if (currentValue !== this.props.value) {
|
|
73189
|
+
if (currentValue !== this.props.value.toString()) {
|
|
72896
73190
|
this.props.onChange(currentValue);
|
|
72897
73191
|
}
|
|
72898
|
-
|
|
73192
|
+
if (!keepFocus) {
|
|
73193
|
+
this.inputRef.el?.blur();
|
|
73194
|
+
}
|
|
72899
73195
|
}
|
|
72900
73196
|
onMouseDown(ev) {
|
|
72901
73197
|
// Stop the event if the input is not focused, we handle everything in onMouseUp
|
|
@@ -72908,13 +73204,25 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72908
73204
|
const target = ev.target;
|
|
72909
73205
|
if (target !== document.activeElement) {
|
|
72910
73206
|
target.focus();
|
|
72911
|
-
|
|
73207
|
+
if (this.props.selectContentOnFocus) {
|
|
73208
|
+
target.select();
|
|
73209
|
+
}
|
|
72912
73210
|
ev.preventDefault();
|
|
72913
73211
|
ev.stopPropagation();
|
|
72914
73212
|
}
|
|
72915
73213
|
}
|
|
73214
|
+
}
|
|
73215
|
+
|
|
73216
|
+
class TextInput extends GenericInput {
|
|
73217
|
+
static template = "o-spreadsheet-TextInput";
|
|
73218
|
+
static components = {};
|
|
73219
|
+
static props = GenericInput.props;
|
|
72916
73220
|
get inputClass() {
|
|
72917
|
-
return [
|
|
73221
|
+
return [
|
|
73222
|
+
this.props.class,
|
|
73223
|
+
"w-100 os-input",
|
|
73224
|
+
this.props.alwaysShowBorder ? "o-input-border" : undefined,
|
|
73225
|
+
]
|
|
72918
73226
|
.filter(isDefined$1)
|
|
72919
73227
|
.join(" ");
|
|
72920
73228
|
}
|
|
@@ -73024,6 +73332,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
73024
73332
|
fontSizeListRef = owl.useRef("fontSizeList");
|
|
73025
73333
|
setup() {
|
|
73026
73334
|
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
73335
|
+
owl.onWillUpdateProps((nextProps) => {
|
|
73336
|
+
if (this.inputRef.el && document.activeElement !== this.inputRef.el) {
|
|
73337
|
+
this.inputRef.el.value = nextProps.currentFontSize;
|
|
73338
|
+
}
|
|
73339
|
+
});
|
|
73340
|
+
owl.onMounted(() => {
|
|
73341
|
+
if (this.inputRef.el) {
|
|
73342
|
+
this.inputRef.el.value = this.props.currentFontSize.toString();
|
|
73343
|
+
}
|
|
73344
|
+
});
|
|
73027
73345
|
}
|
|
73028
73346
|
get popoverProps() {
|
|
73029
73347
|
const { x, y, width, height } = this.rootEditorRef.el.getBoundingClientRect();
|
|
@@ -73917,7 +74235,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
73917
74235
|
|
|
73918
74236
|
class ChartTitle extends owl.Component {
|
|
73919
74237
|
static template = "o-spreadsheet.ChartTitle";
|
|
73920
|
-
static components = { Section, TextStyler };
|
|
74238
|
+
static components = { Section, TextStyler, TextInput };
|
|
73921
74239
|
static props = {
|
|
73922
74240
|
title: { type: String, optional: true },
|
|
73923
74241
|
placeholder: { type: String, optional: true },
|
|
@@ -73931,8 +74249,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
73931
74249
|
title: "",
|
|
73932
74250
|
placeholder: "",
|
|
73933
74251
|
};
|
|
73934
|
-
updateTitle(
|
|
73935
|
-
this.props.updateTitle(
|
|
74252
|
+
updateTitle(value) {
|
|
74253
|
+
this.props.updateTitle(value);
|
|
73936
74254
|
}
|
|
73937
74255
|
}
|
|
73938
74256
|
|
|
@@ -74076,6 +74394,27 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74076
74394
|
}
|
|
74077
74395
|
}
|
|
74078
74396
|
|
|
74397
|
+
class NumberInput extends GenericInput {
|
|
74398
|
+
static template = "o-spreadsheet-NumberInput";
|
|
74399
|
+
static components = {};
|
|
74400
|
+
static props = {
|
|
74401
|
+
...GenericInput.props,
|
|
74402
|
+
min: { type: Number, optional: true },
|
|
74403
|
+
max: { type: Number, optional: true },
|
|
74404
|
+
};
|
|
74405
|
+
// Very short debounce to prevent up/down arrow on number input to spam the onChange
|
|
74406
|
+
debouncedOnChange = debounce(this.props.onChange.bind(this), 100, true);
|
|
74407
|
+
save() {
|
|
74408
|
+
const currentValue = (this.inputRef.el?.value || "").trim();
|
|
74409
|
+
if (currentValue !== this.props.value.toString()) {
|
|
74410
|
+
this.debouncedOnChange(currentValue);
|
|
74411
|
+
}
|
|
74412
|
+
}
|
|
74413
|
+
get inputClass() {
|
|
74414
|
+
return [this.props.class, "o-input"].join(" ");
|
|
74415
|
+
}
|
|
74416
|
+
}
|
|
74417
|
+
|
|
74079
74418
|
class SeriesDesignEditor extends owl.Component {
|
|
74080
74419
|
static template = "o-spreadsheet-SeriesDesignEditor";
|
|
74081
74420
|
static components = {
|
|
@@ -74147,6 +74486,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74147
74486
|
RadioSelection,
|
|
74148
74487
|
Section,
|
|
74149
74488
|
RoundColorPicker,
|
|
74489
|
+
NumberInput,
|
|
74150
74490
|
};
|
|
74151
74491
|
static props = {
|
|
74152
74492
|
chartId: String,
|
|
@@ -74238,9 +74578,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74238
74578
|
get defaultWindowSize() {
|
|
74239
74579
|
return DEFAULT_WINDOW_SIZE;
|
|
74240
74580
|
}
|
|
74241
|
-
onChangeMovingAverageWindow(index,
|
|
74242
|
-
|
|
74243
|
-
let window = parseInt(element.value) || DEFAULT_WINDOW_SIZE;
|
|
74581
|
+
onChangeMovingAverageWindow(index, value) {
|
|
74582
|
+
let window = parseInt(value) || DEFAULT_WINDOW_SIZE;
|
|
74244
74583
|
if (window <= 1) {
|
|
74245
74584
|
window = DEFAULT_WINDOW_SIZE;
|
|
74246
74585
|
}
|
|
@@ -74769,10 +75108,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74769
75108
|
|
|
74770
75109
|
class PieHoleSize extends owl.Component {
|
|
74771
75110
|
static template = "o-spreadsheet.PieHoleSize";
|
|
74772
|
-
static components = { Section };
|
|
75111
|
+
static components = { Section, NumberInput };
|
|
74773
75112
|
static props = { onValueChange: Function, value: Number };
|
|
74774
|
-
// Very short debounce to prevent up/down arrow on number input to spam the onChange
|
|
74775
|
-
debouncedOnChange = debounce(this.onChange.bind(this), 100);
|
|
74776
75113
|
onChange(value) {
|
|
74777
75114
|
if (!isNaN(Number(value))) {
|
|
74778
75115
|
this.props.onValueChange(clip(Number(value), 0, 95));
|
|
@@ -78050,16 +78387,18 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78050
78387
|
|
|
78051
78388
|
class PivotSidePanelStore extends SpreadsheetStore {
|
|
78052
78389
|
pivotId;
|
|
78390
|
+
updateMode;
|
|
78053
78391
|
mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
|
|
78054
|
-
|
|
78392
|
+
_updatesAreDeferred;
|
|
78055
78393
|
draft = null;
|
|
78056
78394
|
notification = this.get(NotificationStore);
|
|
78057
78395
|
alreadyNotified = false;
|
|
78058
78396
|
alreadyNotifiedForPivotSize = false;
|
|
78059
|
-
constructor(get, pivotId) {
|
|
78397
|
+
constructor(get, pivotId, updateMode = "canDefer") {
|
|
78060
78398
|
super(get);
|
|
78061
78399
|
this.pivotId = pivotId;
|
|
78062
|
-
this.
|
|
78400
|
+
this.updateMode = updateMode;
|
|
78401
|
+
this._updatesAreDeferred =
|
|
78063
78402
|
this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
|
|
78064
78403
|
}
|
|
78065
78404
|
handle(cmd) {
|
|
@@ -78070,6 +78409,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78070
78409
|
}
|
|
78071
78410
|
}
|
|
78072
78411
|
}
|
|
78412
|
+
get updatesAreDeferred() {
|
|
78413
|
+
return this.updateMode === "neverDefer" ? false : this._updatesAreDeferred;
|
|
78414
|
+
}
|
|
78073
78415
|
get fields() {
|
|
78074
78416
|
return this.pivot.getFields();
|
|
78075
78417
|
}
|
|
@@ -78144,7 +78486,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78144
78486
|
}
|
|
78145
78487
|
reset(pivotId) {
|
|
78146
78488
|
this.pivotId = pivotId;
|
|
78147
|
-
this.
|
|
78489
|
+
this._updatesAreDeferred = true;
|
|
78148
78490
|
this.draft = null;
|
|
78149
78491
|
}
|
|
78150
78492
|
deferUpdates(shouldDefer) {
|
|
@@ -78155,7 +78497,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78155
78497
|
else {
|
|
78156
78498
|
this.update({ deferUpdates: shouldDefer });
|
|
78157
78499
|
}
|
|
78158
|
-
this.
|
|
78500
|
+
this._updatesAreDeferred = shouldDefer;
|
|
78159
78501
|
}
|
|
78160
78502
|
applyUpdate() {
|
|
78161
78503
|
if (this.draft) {
|
|
@@ -78409,6 +78751,26 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78409
78751
|
editor: PivotSpreadsheetSidePanel,
|
|
78410
78752
|
});
|
|
78411
78753
|
|
|
78754
|
+
class PivotDesignPanel extends owl.Component {
|
|
78755
|
+
static template = "o-spreadsheet-PivotDesignPanel";
|
|
78756
|
+
static props = { pivotId: String };
|
|
78757
|
+
static components = { Section, Checkbox };
|
|
78758
|
+
store;
|
|
78759
|
+
setup() {
|
|
78760
|
+
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId, "neverDefer");
|
|
78761
|
+
}
|
|
78762
|
+
updatePivotStyleProperty(key, value) {
|
|
78763
|
+
this.store.update({ style: { ...this.pivotStyle, [key]: value } });
|
|
78764
|
+
}
|
|
78765
|
+
get pivotStyle() {
|
|
78766
|
+
const pivot = this.env.model.getters.getPivotCoreDefinition(this.props.pivotId);
|
|
78767
|
+
return pivot.style || {};
|
|
78768
|
+
}
|
|
78769
|
+
get defaultStyle() {
|
|
78770
|
+
return DEFAULT_PIVOT_STYLE;
|
|
78771
|
+
}
|
|
78772
|
+
}
|
|
78773
|
+
|
|
78412
78774
|
class PivotSidePanel extends owl.Component {
|
|
78413
78775
|
static template = "o-spreadsheet-PivotSidePanel";
|
|
78414
78776
|
static props = {
|
|
@@ -78418,6 +78780,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78418
78780
|
static components = {
|
|
78419
78781
|
PivotLayoutConfigurator,
|
|
78420
78782
|
Section,
|
|
78783
|
+
PivotDesignPanel,
|
|
78784
|
+
};
|
|
78785
|
+
state = owl.useState({ panel: "configuration" });
|
|
78786
|
+
panelContentRef = owl.useRef("panelContent");
|
|
78787
|
+
scrollPositions = {
|
|
78788
|
+
configuration: 0,
|
|
78789
|
+
design: 0,
|
|
78421
78790
|
};
|
|
78422
78791
|
setup() {
|
|
78423
78792
|
useHighlights(this);
|
|
@@ -78432,6 +78801,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78432
78801
|
get highlights() {
|
|
78433
78802
|
return getPivotHighlights(this.env.model.getters, this.props.pivotId);
|
|
78434
78803
|
}
|
|
78804
|
+
switchPanel(panel) {
|
|
78805
|
+
const el = this.panelContentRef.el;
|
|
78806
|
+
if (el) {
|
|
78807
|
+
this.scrollPositions[this.state.panel] = el.scrollTop;
|
|
78808
|
+
}
|
|
78809
|
+
this.state.panel = panel;
|
|
78810
|
+
}
|
|
78435
78811
|
}
|
|
78436
78812
|
|
|
78437
78813
|
class RemoveDuplicatesPanel extends owl.Component {
|
|
@@ -78937,7 +79313,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78937
79313
|
|
|
78938
79314
|
class TablePanel extends owl.Component {
|
|
78939
79315
|
static template = "o-spreadsheet-TablePanel";
|
|
78940
|
-
static components = {
|
|
79316
|
+
static components = {
|
|
79317
|
+
TableStylePicker,
|
|
79318
|
+
SelectionInput,
|
|
79319
|
+
ValidationMessages,
|
|
79320
|
+
Checkbox,
|
|
79321
|
+
Section,
|
|
79322
|
+
NumberInput,
|
|
79323
|
+
};
|
|
78941
79324
|
static props = { onCloseSidePanel: Function, table: Object };
|
|
78942
79325
|
state;
|
|
78943
79326
|
setup() {
|
|
@@ -78987,13 +79370,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78987
79370
|
this.state.tableZoneErrors = [];
|
|
78988
79371
|
}
|
|
78989
79372
|
}
|
|
78990
|
-
onChangeNumberOfHeaders(
|
|
78991
|
-
const
|
|
78992
|
-
|
|
78993
|
-
const result = this.updateNumberOfHeaders(numberOfHeaders);
|
|
78994
|
-
if (!result.isSuccessful) {
|
|
78995
|
-
input.value = this.props.table.config.numberOfHeaders.toString();
|
|
78996
|
-
}
|
|
79373
|
+
onChangeNumberOfHeaders(value) {
|
|
79374
|
+
const numberOfHeaders = parseInt(value);
|
|
79375
|
+
this.updateNumberOfHeaders(numberOfHeaders);
|
|
78997
79376
|
}
|
|
78998
79377
|
updateNumberOfHeaders(numberOfHeaders) {
|
|
78999
79378
|
const hasFilters = numberOfHeaders > 0 && (this.tableConfig.hasFilters || this.state.filtersEnabledIfPossible);
|
|
@@ -87478,6 +87857,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
87478
87857
|
GeoChartRegionSelectSection,
|
|
87479
87858
|
ChartDashboardMenu,
|
|
87480
87859
|
FullScreenFigure,
|
|
87860
|
+
NumberInput,
|
|
87481
87861
|
};
|
|
87482
87862
|
const hooks = {
|
|
87483
87863
|
useDragAndDropListItems,
|
|
@@ -87581,9 +87961,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
87581
87961
|
exports.tokenize = tokenize;
|
|
87582
87962
|
|
|
87583
87963
|
|
|
87584
|
-
__info__.version = "19.1.0-alpha.
|
|
87585
|
-
__info__.date = "2025-10-
|
|
87586
|
-
__info__.hash = "
|
|
87964
|
+
__info__.version = "19.1.0-alpha.9";
|
|
87965
|
+
__info__.date = "2025-10-23T11:12:55.400Z";
|
|
87966
|
+
__info__.hash = "bd756dd";
|
|
87587
87967
|
|
|
87588
87968
|
|
|
87589
87969
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|