@odoo/o-spreadsheet 19.1.0-alpha.7 → 19.1.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.
- package/dist/o-spreadsheet-engine.d.ts +8 -20
- package/dist/o-spreadsheet-engine.esm.js +471 -189
- package/dist/o-spreadsheet-engine.iife.js +471 -189
- package/dist/o-spreadsheet-engine.min.iife.js +313 -313
- package/dist/o-spreadsheet.d.ts +17 -23
- package/dist/o_spreadsheet.esm.js +696 -371
- package/dist/o_spreadsheet.iife.js +696 -371
- package/dist/o_spreadsheet.min.iife.js +317 -317
- package/dist/o_spreadsheet.xml +98 -9
- 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.8
|
|
6
|
+
* @date 2025-10-23T08:20:05.310Z
|
|
7
|
+
* @hash 78717d4
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports, owl) {
|
|
@@ -1154,6 +1154,29 @@
|
|
|
1154
1154
|
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
1155
1155
|
}
|
|
1156
1156
|
}
|
|
1157
|
+
function profilesContainsZone(profilesStartingPosition, profiles, zone) {
|
|
1158
|
+
const leftValue = zone.left;
|
|
1159
|
+
const rightValue = zone.right;
|
|
1160
|
+
const topValue = zone.top;
|
|
1161
|
+
const bottomValue = zone.bottom + 1;
|
|
1162
|
+
const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
|
|
1163
|
+
const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
|
|
1164
|
+
if (leftIndex === -1 || rightIndex === -1) {
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
1168
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
1169
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
|
|
1170
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
|
|
1171
|
+
if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
1157
1180
|
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
1158
1181
|
if (value === undefined) {
|
|
1159
1182
|
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
@@ -1238,7 +1261,18 @@
|
|
|
1238
1261
|
}
|
|
1239
1262
|
// add the top and bottom value to the profile and
|
|
1240
1263
|
// remove all information between the top and bottom index
|
|
1241
|
-
|
|
1264
|
+
const toDelete = bottomSuccIndex - topPredIndex - 1;
|
|
1265
|
+
const toInsert = newPoints.length;
|
|
1266
|
+
const start = topPredIndex + 1;
|
|
1267
|
+
// fast path and slow path
|
|
1268
|
+
if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
|
|
1269
|
+
// fast path: we just need to replace the last element
|
|
1270
|
+
profile[start] = newPoints[0] ?? newPoints[1];
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
// equivalent but slower and with memory allocation
|
|
1274
|
+
profile.splice(start, toDelete, ...newPoints);
|
|
1275
|
+
}
|
|
1242
1276
|
}
|
|
1243
1277
|
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
1244
1278
|
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
@@ -1277,8 +1311,10 @@
|
|
|
1277
1311
|
left,
|
|
1278
1312
|
bottom,
|
|
1279
1313
|
right,
|
|
1280
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
1281
1314
|
};
|
|
1315
|
+
if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
|
|
1316
|
+
profileZone.hasHeader = true;
|
|
1317
|
+
}
|
|
1282
1318
|
let findCorrespondingZone = false;
|
|
1283
1319
|
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
1284
1320
|
const pendingZone = pendingZones[j];
|
|
@@ -1763,17 +1799,6 @@
|
|
|
1763
1799
|
}
|
|
1764
1800
|
return [leftColumnZone, rightPartZone];
|
|
1765
1801
|
}
|
|
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
1802
|
/**
|
|
1778
1803
|
* Array of all positions in the zone.
|
|
1779
1804
|
*/
|
|
@@ -14882,6 +14907,13 @@
|
|
|
14882
14907
|
.add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
|
|
14883
14908
|
.add("second_number", nullHandlerDecorator(secondNumberAdapter));
|
|
14884
14909
|
|
|
14910
|
+
const DEFAULT_PIVOT_STYLE = {
|
|
14911
|
+
displayTotals: true,
|
|
14912
|
+
displayColumnHeaders: true,
|
|
14913
|
+
displayMeasuresRow: true,
|
|
14914
|
+
numberOfRows: Number.MAX_VALUE,
|
|
14915
|
+
numberOfColumns: Number.MAX_VALUE,
|
|
14916
|
+
};
|
|
14885
14917
|
const AGGREGATOR_NAMES = {
|
|
14886
14918
|
count: _t$1("Count"),
|
|
14887
14919
|
count_distinct: _t$1("Count Distinct"),
|
|
@@ -15215,6 +15247,25 @@
|
|
|
15215
15247
|
pivot: { ...definition, collapsedDomains: newDomains },
|
|
15216
15248
|
});
|
|
15217
15249
|
}
|
|
15250
|
+
function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
|
|
15251
|
+
const style = definition.style;
|
|
15252
|
+
const numberOfRows = rowCountArg !== undefined
|
|
15253
|
+
? toNumber(rowCountArg, locale)
|
|
15254
|
+
: style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
|
|
15255
|
+
const numberOfColumns = columnCountArg !== undefined
|
|
15256
|
+
? toNumber(columnCountArg, locale)
|
|
15257
|
+
: style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
|
|
15258
|
+
const displayTotals = includeTotalArg !== undefined
|
|
15259
|
+
? toBoolean(includeTotalArg)
|
|
15260
|
+
: style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
|
|
15261
|
+
const displayColumnHeaders = includeColumnHeadersArg !== undefined
|
|
15262
|
+
? toBoolean(includeColumnHeadersArg)
|
|
15263
|
+
: style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
|
|
15264
|
+
const displayMeasuresRow = includeMeasuresRowArg !== undefined
|
|
15265
|
+
? toBoolean(includeMeasuresRowArg)
|
|
15266
|
+
: style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
|
|
15267
|
+
return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
|
|
15268
|
+
}
|
|
15218
15269
|
|
|
15219
15270
|
/**
|
|
15220
15271
|
* Get the pivot ID from the formula pivot ID.
|
|
@@ -15829,24 +15880,18 @@
|
|
|
15829
15880
|
arg("column_count (number, optional)", _t$1("number of columns")),
|
|
15830
15881
|
arg("include_measure_titles (boolean, default=TRUE)", _t$1("Whether to include the measure titles row or not.")),
|
|
15831
15882
|
],
|
|
15832
|
-
compute: function (pivotFormulaId, rowCount
|
|
15883
|
+
compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
|
|
15833
15884
|
const _pivotFormulaId = toString(pivotFormulaId);
|
|
15834
|
-
const
|
|
15835
|
-
|
|
15885
|
+
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15886
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
15887
|
+
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15888
|
+
const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
|
|
15889
|
+
if (pivotStyle.numberOfRows < 0) {
|
|
15836
15890
|
return new EvaluationError(_t$1("The number of rows must be positive."));
|
|
15837
15891
|
}
|
|
15838
|
-
|
|
15839
|
-
if (_columnCount < 0) {
|
|
15892
|
+
if (pivotStyle.numberOfColumns < 0) {
|
|
15840
15893
|
return new EvaluationError(_t$1("The number of columns must be positive."));
|
|
15841
15894
|
}
|
|
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
15895
|
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
15851
15896
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
15852
15897
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
@@ -15857,20 +15902,20 @@
|
|
|
15857
15902
|
if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
|
|
15858
15903
|
return new EvaluationError(getPivotTooBigErrorMessage$1(table.numberOfCells, this.locale));
|
|
15859
15904
|
}
|
|
15860
|
-
const cells = table.getPivotCells(
|
|
15905
|
+
const cells = table.getPivotCells(pivotStyle);
|
|
15861
15906
|
let headerRows = 0;
|
|
15862
|
-
if (
|
|
15907
|
+
if (pivotStyle.displayColumnHeaders) {
|
|
15863
15908
|
headerRows = table.columns.length - 1;
|
|
15864
15909
|
}
|
|
15865
|
-
if (
|
|
15910
|
+
if (pivotStyle.displayMeasuresRow) {
|
|
15866
15911
|
headerRows++;
|
|
15867
15912
|
}
|
|
15868
15913
|
const pivotTitle = this.getters.getPivotName(pivotId);
|
|
15869
|
-
const tableHeight = Math.min(headerRows +
|
|
15914
|
+
const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
|
|
15870
15915
|
if (tableHeight === 0) {
|
|
15871
15916
|
return [[{ value: pivotTitle }]];
|
|
15872
15917
|
}
|
|
15873
|
-
const tableWidth = Math.min(1 +
|
|
15918
|
+
const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
|
|
15874
15919
|
const result = [];
|
|
15875
15920
|
for (const col of range$1(0, tableWidth)) {
|
|
15876
15921
|
result[col] = [];
|
|
@@ -15893,7 +15938,7 @@
|
|
|
15893
15938
|
}
|
|
15894
15939
|
}
|
|
15895
15940
|
}
|
|
15896
|
-
if (
|
|
15941
|
+
if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
|
|
15897
15942
|
result[0][0] = { value: pivotTitle };
|
|
15898
15943
|
}
|
|
15899
15944
|
return result;
|
|
@@ -19495,6 +19540,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
19495
19540
|
}
|
|
19496
19541
|
return parts;
|
|
19497
19542
|
}
|
|
19543
|
+
function positionToBoundedRange(position) {
|
|
19544
|
+
const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
|
|
19545
|
+
return { sheetId: position.sheetId, zone };
|
|
19546
|
+
}
|
|
19498
19547
|
/**
|
|
19499
19548
|
* Check that a zone is valid regarding the order of top-bottom and left-right.
|
|
19500
19549
|
* Left should be smaller than right, top should be smaller than bottom.
|
|
@@ -21361,6 +21410,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21361
21410
|
backgroundColor,
|
|
21362
21411
|
yAxisID: definition.horizontal ? "y" : definition.dataSets?.[index].yAxisId || "y",
|
|
21363
21412
|
xAxisID: "x",
|
|
21413
|
+
barPercentage: 0.9,
|
|
21414
|
+
categoryPercentage: dataSetsValues.length > 1 ? 0.8 : 1,
|
|
21415
|
+
borderRadius: 2,
|
|
21364
21416
|
};
|
|
21365
21417
|
dataSets.push(dataset);
|
|
21366
21418
|
const trendConfig = definition.dataSets?.[index].trend;
|
|
@@ -21489,6 +21541,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21489
21541
|
const dataSets = [];
|
|
21490
21542
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
21491
21543
|
const trendDatasets = [];
|
|
21544
|
+
const barDatasets = dataSetsValues.filter((_, i) => (definition.dataSets?.[i].type ?? "line") === "bar");
|
|
21492
21545
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
21493
21546
|
let { label, data, hidden } = dataSetsValues[index];
|
|
21494
21547
|
label = definition.dataSets?.[index].label || label;
|
|
@@ -21507,6 +21560,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
21507
21560
|
order: type === "bar" ? dataSetsValues.length + index : index,
|
|
21508
21561
|
pointRadius: definition.hideDataMarkers ? 0 : LINE_DATA_POINT_RADIUS,
|
|
21509
21562
|
};
|
|
21563
|
+
if (dataset.type === "bar") {
|
|
21564
|
+
dataset.barPercentage = 0.9;
|
|
21565
|
+
dataset.categoryPercentage = barDatasets.length > 1 ? 0.8 : 1;
|
|
21566
|
+
dataset.borderRadius = 2;
|
|
21567
|
+
}
|
|
21510
21568
|
dataSets.push(dataset);
|
|
21511
21569
|
const trendConfig = definition.dataSets?.[index].trend;
|
|
21512
21570
|
const trendData = args.trendDataSetsValues?.[index];
|
|
@@ -33489,6 +33547,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33489
33547
|
return data;
|
|
33490
33548
|
}
|
|
33491
33549
|
const figureIds = new Set();
|
|
33550
|
+
const chartIds = new Set();
|
|
33492
33551
|
const uuidGenerator = new UuidGenerator();
|
|
33493
33552
|
for (const sheet of data.sheets || []) {
|
|
33494
33553
|
for (const figure of sheet.figures || []) {
|
|
@@ -33496,6 +33555,12 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33496
33555
|
figure.id += uuidGenerator.smallUuid();
|
|
33497
33556
|
}
|
|
33498
33557
|
figureIds.add(figure.id);
|
|
33558
|
+
if (figure.tag === "chart") {
|
|
33559
|
+
if (chartIds.has(figure.data?.chartId)) {
|
|
33560
|
+
figure.data.chartId += uuidGenerator.smallUuid();
|
|
33561
|
+
}
|
|
33562
|
+
chartIds.add(figure.data?.chartId);
|
|
33563
|
+
}
|
|
33499
33564
|
}
|
|
33500
33565
|
}
|
|
33501
33566
|
data.uniqueFigureIds = true;
|
|
@@ -33863,11 +33928,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33863
33928
|
* @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
|
|
33864
33929
|
*/
|
|
33865
33930
|
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
33931
|
}
|
|
33872
33932
|
|
|
33873
33933
|
class BordersPlugin extends CorePlugin {
|
|
@@ -37711,17 +37771,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
37711
37771
|
break;
|
|
37712
37772
|
}
|
|
37713
37773
|
}
|
|
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
37774
|
// ---------------------------------------------------------------------------
|
|
37726
37775
|
// Getters
|
|
37727
37776
|
// ---------------------------------------------------------------------------
|
|
@@ -37783,13 +37832,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
37783
37832
|
sheet.images = [...sheet.images, ...images];
|
|
37784
37833
|
}
|
|
37785
37834
|
}
|
|
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
37835
|
}
|
|
37794
37836
|
|
|
37795
37837
|
class MergePlugin extends CorePlugin {
|
|
@@ -38562,26 +38604,22 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38562
38604
|
getNumberOfDataColumns() {
|
|
38563
38605
|
return this.columns.at(-1)?.length || 0;
|
|
38564
38606
|
}
|
|
38565
|
-
getSkippedRows(
|
|
38607
|
+
getSkippedRows(pivotStyle) {
|
|
38566
38608
|
const skippedRows = new Set();
|
|
38567
|
-
if (!
|
|
38609
|
+
if (!pivotStyle.displayColumnHeaders) {
|
|
38568
38610
|
for (let i = 0; i < this.columns.length - 1; i++) {
|
|
38569
38611
|
skippedRows.add(i);
|
|
38570
38612
|
}
|
|
38571
38613
|
}
|
|
38572
|
-
if (!
|
|
38614
|
+
if (!pivotStyle.displayMeasuresRow) {
|
|
38573
38615
|
skippedRows.add(this.columns.length - 1);
|
|
38574
38616
|
}
|
|
38575
38617
|
return skippedRows;
|
|
38576
38618
|
}
|
|
38577
|
-
getPivotCells(
|
|
38578
|
-
|
|
38579
|
-
displayTotals: true,
|
|
38580
|
-
displayMeasuresRow: true,
|
|
38581
|
-
}) {
|
|
38582
|
-
const key = JSON.stringify(visibilityOptions);
|
|
38619
|
+
getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
|
|
38620
|
+
const key = JSON.stringify(pivotStyle);
|
|
38583
38621
|
if (!this.pivotCells[key]) {
|
|
38584
|
-
const { displayTotals } =
|
|
38622
|
+
const { displayTotals } = pivotStyle;
|
|
38585
38623
|
const numberOfDataRows = this.rows.length;
|
|
38586
38624
|
const numberOfDataColumns = this.getNumberOfDataColumns();
|
|
38587
38625
|
let pivotHeight = this.columns.length + numberOfDataRows;
|
|
@@ -38593,7 +38631,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38593
38631
|
pivotWidth -= this.measures.length;
|
|
38594
38632
|
}
|
|
38595
38633
|
const domainArray = [];
|
|
38596
|
-
const skippedRows = this.getSkippedRows(
|
|
38634
|
+
const skippedRows = this.getSkippedRows(pivotStyle);
|
|
38597
38635
|
for (let col = 0; col < pivotWidth; col++) {
|
|
38598
38636
|
domainArray.push([]);
|
|
38599
38637
|
for (let row = 0; row < pivotHeight; row++) {
|
|
@@ -41685,6 +41723,281 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41685
41723
|
}
|
|
41686
41724
|
}
|
|
41687
41725
|
|
|
41726
|
+
class ZoneSet {
|
|
41727
|
+
profilesStartingPosition = [0];
|
|
41728
|
+
profiles = new Map([[0, []]]);
|
|
41729
|
+
constructor(zones = []) {
|
|
41730
|
+
for (const zone of zones) {
|
|
41731
|
+
this.add(zone);
|
|
41732
|
+
}
|
|
41733
|
+
}
|
|
41734
|
+
isEmpty() {
|
|
41735
|
+
return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
|
|
41736
|
+
}
|
|
41737
|
+
add(zone) {
|
|
41738
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
|
|
41739
|
+
}
|
|
41740
|
+
delete(zone) {
|
|
41741
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
|
|
41742
|
+
}
|
|
41743
|
+
has(zone) {
|
|
41744
|
+
return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
|
|
41745
|
+
}
|
|
41746
|
+
difference(other) {
|
|
41747
|
+
const result = this.copy();
|
|
41748
|
+
for (const zone of other) {
|
|
41749
|
+
result.delete(zone);
|
|
41750
|
+
}
|
|
41751
|
+
return result;
|
|
41752
|
+
}
|
|
41753
|
+
copy() {
|
|
41754
|
+
const result = new ZoneSet();
|
|
41755
|
+
result.profilesStartingPosition = [...this.profilesStartingPosition];
|
|
41756
|
+
result.profiles = new Map();
|
|
41757
|
+
for (const [key, value] of this.profiles) {
|
|
41758
|
+
result.profiles.set(key, [...value]);
|
|
41759
|
+
}
|
|
41760
|
+
return result;
|
|
41761
|
+
}
|
|
41762
|
+
size() {
|
|
41763
|
+
let size = 0;
|
|
41764
|
+
for (const profile of this.profiles.values()) {
|
|
41765
|
+
size += profile.length;
|
|
41766
|
+
}
|
|
41767
|
+
return size / 2;
|
|
41768
|
+
}
|
|
41769
|
+
/**
|
|
41770
|
+
* iterator of all the zones in the ZoneSet
|
|
41771
|
+
*/
|
|
41772
|
+
[Symbol.iterator]() {
|
|
41773
|
+
return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
|
|
41774
|
+
}
|
|
41775
|
+
}
|
|
41776
|
+
|
|
41777
|
+
class RangeSet {
|
|
41778
|
+
setsBySheetId = {};
|
|
41779
|
+
constructor(ranges = []) {
|
|
41780
|
+
for (const range of ranges) {
|
|
41781
|
+
this.add(range);
|
|
41782
|
+
}
|
|
41783
|
+
}
|
|
41784
|
+
add(range) {
|
|
41785
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41786
|
+
this.setsBySheetId[range.sheetId] = new ZoneSet();
|
|
41787
|
+
}
|
|
41788
|
+
this.setsBySheetId[range.sheetId].add(range.zone);
|
|
41789
|
+
}
|
|
41790
|
+
addMany(ranges) {
|
|
41791
|
+
for (const range of ranges) {
|
|
41792
|
+
this.add(range);
|
|
41793
|
+
}
|
|
41794
|
+
}
|
|
41795
|
+
addPosition(position) {
|
|
41796
|
+
this.add(positionToBoundedRange(position));
|
|
41797
|
+
}
|
|
41798
|
+
addManyPositions(positions) {
|
|
41799
|
+
for (const position of positions) {
|
|
41800
|
+
this.addPosition(position);
|
|
41801
|
+
}
|
|
41802
|
+
}
|
|
41803
|
+
has(range) {
|
|
41804
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41805
|
+
return false;
|
|
41806
|
+
}
|
|
41807
|
+
return this.setsBySheetId[range.sheetId].has(range.zone);
|
|
41808
|
+
}
|
|
41809
|
+
hasPosition(position) {
|
|
41810
|
+
return this.has(positionToBoundedRange(position));
|
|
41811
|
+
}
|
|
41812
|
+
delete(range) {
|
|
41813
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
41814
|
+
return;
|
|
41815
|
+
}
|
|
41816
|
+
this.setsBySheetId[range.sheetId].delete(range.zone);
|
|
41817
|
+
}
|
|
41818
|
+
deleteMany(ranges) {
|
|
41819
|
+
for (const range of ranges) {
|
|
41820
|
+
this.delete(range);
|
|
41821
|
+
}
|
|
41822
|
+
}
|
|
41823
|
+
deleteManyPositions(positions) {
|
|
41824
|
+
for (const position of positions) {
|
|
41825
|
+
this.delete(positionToBoundedRange(position));
|
|
41826
|
+
}
|
|
41827
|
+
}
|
|
41828
|
+
difference(other) {
|
|
41829
|
+
const result = new RangeSet();
|
|
41830
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41831
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
|
|
41832
|
+
}
|
|
41833
|
+
for (const sheetId in other.setsBySheetId) {
|
|
41834
|
+
if (result.setsBySheetId[sheetId]) {
|
|
41835
|
+
result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
|
|
41836
|
+
}
|
|
41837
|
+
}
|
|
41838
|
+
return result;
|
|
41839
|
+
}
|
|
41840
|
+
copy() {
|
|
41841
|
+
const result = new RangeSet();
|
|
41842
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41843
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
|
|
41844
|
+
}
|
|
41845
|
+
return result;
|
|
41846
|
+
}
|
|
41847
|
+
clear() {
|
|
41848
|
+
this.setsBySheetId = {};
|
|
41849
|
+
}
|
|
41850
|
+
size() {
|
|
41851
|
+
let size = 0;
|
|
41852
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41853
|
+
size += this.setsBySheetId[sheetId].size();
|
|
41854
|
+
}
|
|
41855
|
+
return size;
|
|
41856
|
+
}
|
|
41857
|
+
isEmpty() {
|
|
41858
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41859
|
+
if (!this.setsBySheetId[sheetId].isEmpty()) {
|
|
41860
|
+
return false;
|
|
41861
|
+
}
|
|
41862
|
+
}
|
|
41863
|
+
return true;
|
|
41864
|
+
}
|
|
41865
|
+
/**
|
|
41866
|
+
* iterator of all the ranges in the RangeSet
|
|
41867
|
+
*/
|
|
41868
|
+
[Symbol.iterator]() {
|
|
41869
|
+
const result = [];
|
|
41870
|
+
for (const sheetId in this.setsBySheetId) {
|
|
41871
|
+
for (const zone of this.setsBySheetId[sheetId]) {
|
|
41872
|
+
result.push({ sheetId: sheetId, zone });
|
|
41873
|
+
}
|
|
41874
|
+
}
|
|
41875
|
+
return result[Symbol.iterator]();
|
|
41876
|
+
}
|
|
41877
|
+
}
|
|
41878
|
+
|
|
41879
|
+
/**
|
|
41880
|
+
* R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
|
|
41881
|
+
* Ranges associated to the exact same bounding box are grouped together
|
|
41882
|
+
* to reduce the number of nodes in the R-tree.
|
|
41883
|
+
*/
|
|
41884
|
+
class DependenciesRTree {
|
|
41885
|
+
rTree;
|
|
41886
|
+
constructor(items = []) {
|
|
41887
|
+
const compactedBoxes = groupSameBoundingBoxes(items);
|
|
41888
|
+
this.rTree = new SpreadsheetRTree(compactedBoxes);
|
|
41889
|
+
}
|
|
41890
|
+
insert(item) {
|
|
41891
|
+
const data = this.rTree.search(item.boundingBox);
|
|
41892
|
+
const itemBoundingBox = item.boundingBox;
|
|
41893
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
41894
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
41895
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
41896
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
41897
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
41898
|
+
if (exactBoundingBox) {
|
|
41899
|
+
exactBoundingBox.data.add(item.data);
|
|
41900
|
+
}
|
|
41901
|
+
else {
|
|
41902
|
+
this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
|
|
41903
|
+
}
|
|
41904
|
+
}
|
|
41905
|
+
search({ zone, sheetId }) {
|
|
41906
|
+
const results = new RangeSet();
|
|
41907
|
+
for (const { data } of this.rTree.search({ zone, sheetId })) {
|
|
41908
|
+
results.addMany(data);
|
|
41909
|
+
}
|
|
41910
|
+
return results;
|
|
41911
|
+
}
|
|
41912
|
+
remove(item) {
|
|
41913
|
+
const data = this.rTree.search(item.boundingBox);
|
|
41914
|
+
const itemBoundingBox = item.boundingBox;
|
|
41915
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
41916
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
41917
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
41918
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
41919
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
41920
|
+
if (exactBoundingBox) {
|
|
41921
|
+
exactBoundingBox.data.delete(item.data);
|
|
41922
|
+
}
|
|
41923
|
+
else {
|
|
41924
|
+
this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
|
|
41925
|
+
}
|
|
41926
|
+
}
|
|
41927
|
+
}
|
|
41928
|
+
/**
|
|
41929
|
+
* Group together all formulas pointing to the exact same dependency (bounding box).
|
|
41930
|
+
* The goal is to optimize the following case:
|
|
41931
|
+
* - if any cell in B1:B1000 changes, C1 must be recomputed
|
|
41932
|
+
* - if any cell in B1:B1000 changes, C2 must be recomputed
|
|
41933
|
+
* - if any cell in B1:B1000 changes, C3 must be recomputed
|
|
41934
|
+
* ...
|
|
41935
|
+
* - if any cell in B1:B1000 changes, C1000 must be recomputed
|
|
41936
|
+
*
|
|
41937
|
+
* Instead of having 1000 entries in the R-tree, we want to have a single entry
|
|
41938
|
+
* with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
|
|
41939
|
+
*/
|
|
41940
|
+
function groupSameBoundingBoxes(items) {
|
|
41941
|
+
// Important: this function must be as fast as possible. It is on the evaluation hot path.
|
|
41942
|
+
let maxCol = 0;
|
|
41943
|
+
let maxRow = 0;
|
|
41944
|
+
for (let i = 0; i < items.length; i++) {
|
|
41945
|
+
const zone = items[i].boundingBox.zone;
|
|
41946
|
+
if (zone.right > maxCol) {
|
|
41947
|
+
maxCol = zone.right;
|
|
41948
|
+
}
|
|
41949
|
+
if (zone.bottom > maxRow) {
|
|
41950
|
+
maxRow = zone.bottom;
|
|
41951
|
+
}
|
|
41952
|
+
}
|
|
41953
|
+
maxCol += 1;
|
|
41954
|
+
maxRow += 1;
|
|
41955
|
+
// in most real-world cases, we can use a fast numeric key
|
|
41956
|
+
// but if the zones are too far right or bottom, we fallback to a slower string key
|
|
41957
|
+
const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
|
|
41958
|
+
const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
|
|
41959
|
+
if (!useFastKey) {
|
|
41960
|
+
console.warn("Max col/row size exceeded, using slow zone key");
|
|
41961
|
+
}
|
|
41962
|
+
const groupedByBBox = {};
|
|
41963
|
+
for (const item of items) {
|
|
41964
|
+
const sheetId = item.boundingBox.sheetId;
|
|
41965
|
+
if (!groupedByBBox[sheetId]) {
|
|
41966
|
+
groupedByBBox[sheetId] = {};
|
|
41967
|
+
}
|
|
41968
|
+
const bBox = item.boundingBox.zone;
|
|
41969
|
+
let bBoxKey = 0;
|
|
41970
|
+
if (useFastKey) {
|
|
41971
|
+
bBoxKey =
|
|
41972
|
+
bBox.left +
|
|
41973
|
+
bBox.top * maxCol +
|
|
41974
|
+
bBox.right * maxCol * maxRow +
|
|
41975
|
+
bBox.bottom * maxCol * maxRow * maxCol;
|
|
41976
|
+
}
|
|
41977
|
+
else {
|
|
41978
|
+
bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
|
|
41979
|
+
}
|
|
41980
|
+
if (groupedByBBox[sheetId][bBoxKey]) {
|
|
41981
|
+
const ranges = groupedByBBox[sheetId][bBoxKey].data;
|
|
41982
|
+
ranges.add(item.data);
|
|
41983
|
+
}
|
|
41984
|
+
else {
|
|
41985
|
+
groupedByBBox[sheetId][bBoxKey] = {
|
|
41986
|
+
boundingBox: item.boundingBox,
|
|
41987
|
+
data: new RangeSet([item.data]),
|
|
41988
|
+
};
|
|
41989
|
+
}
|
|
41990
|
+
}
|
|
41991
|
+
const result = [];
|
|
41992
|
+
for (const sheetId in groupedByBBox) {
|
|
41993
|
+
const map = groupedByBBox[sheetId];
|
|
41994
|
+
for (const key in map) {
|
|
41995
|
+
result.push(map[key]);
|
|
41996
|
+
}
|
|
41997
|
+
}
|
|
41998
|
+
return result;
|
|
41999
|
+
}
|
|
42000
|
+
|
|
41688
42001
|
/**
|
|
41689
42002
|
* Implementation of a dependency Graph.
|
|
41690
42003
|
* The graph is used to evaluate the cells in the correct
|
|
@@ -41693,12 +42006,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41693
42006
|
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
41694
42007
|
*/
|
|
41695
42008
|
class FormulaDependencyGraph {
|
|
41696
|
-
createEmptyPositionSet;
|
|
41697
42009
|
dependencies = new PositionMap();
|
|
41698
42010
|
rTree;
|
|
41699
|
-
constructor(
|
|
41700
|
-
this.
|
|
41701
|
-
this.rTree = new SpreadsheetRTree(data);
|
|
42011
|
+
constructor(data = []) {
|
|
42012
|
+
this.rTree = new DependenciesRTree(data);
|
|
41702
42013
|
}
|
|
41703
42014
|
removeAllDependencies(formulaPosition) {
|
|
41704
42015
|
const ranges = this.dependencies.get(formulaPosition);
|
|
@@ -41712,7 +42023,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41712
42023
|
}
|
|
41713
42024
|
addDependencies(formulaPosition, dependencies) {
|
|
41714
42025
|
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
41715
|
-
data:
|
|
42026
|
+
data: {
|
|
42027
|
+
sheetId: formulaPosition.sheetId,
|
|
42028
|
+
zone: positionToZone(formulaPosition),
|
|
42029
|
+
},
|
|
41716
42030
|
boundingBox: {
|
|
41717
42031
|
zone,
|
|
41718
42032
|
sheetId,
|
|
@@ -41730,46 +42044,20 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41730
42044
|
}
|
|
41731
42045
|
}
|
|
41732
42046
|
/**
|
|
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)
|
|
42047
|
+
* Return all the cells that depend on the provided ranges.
|
|
41736
42048
|
*/
|
|
41737
|
-
getCellsDependingOn(ranges,
|
|
41738
|
-
|
|
42049
|
+
getCellsDependingOn(ranges, visited = new RangeSet()) {
|
|
42050
|
+
visited = visited.copy();
|
|
41739
42051
|
const queue = Array.from(ranges).reverse();
|
|
41740
42052
|
while (queue.length > 0) {
|
|
41741
42053
|
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
|
-
}
|
|
42054
|
+
visited.add(range);
|
|
42055
|
+
const impactedRanges = this.rTree.search(range);
|
|
42056
|
+
queue.push(...impactedRanges.difference(visited));
|
|
41763
42057
|
}
|
|
41764
42058
|
// remove initial ranges
|
|
41765
42059
|
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
|
-
}
|
|
42060
|
+
visited.delete(range);
|
|
41773
42061
|
}
|
|
41774
42062
|
return visited;
|
|
41775
42063
|
}
|
|
@@ -42046,7 +42334,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42046
42334
|
getters;
|
|
42047
42335
|
compilationParams;
|
|
42048
42336
|
evaluatedCells = new PositionMap();
|
|
42049
|
-
formulaDependencies = lazy(new FormulaDependencyGraph(
|
|
42337
|
+
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
42050
42338
|
blockedArrayFormulas = new PositionSet({});
|
|
42051
42339
|
spreadingRelations = new SpreadingRelation();
|
|
42052
42340
|
constructor(context, getters) {
|
|
@@ -42081,7 +42369,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42081
42369
|
return undefined;
|
|
42082
42370
|
}
|
|
42083
42371
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
42084
|
-
return
|
|
42372
|
+
return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
|
|
42085
42373
|
}
|
|
42086
42374
|
updateDependencies(position) {
|
|
42087
42375
|
// removing dependencies is slow because it requires
|
|
@@ -42125,57 +42413,72 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42125
42413
|
}
|
|
42126
42414
|
evaluateCells(positions) {
|
|
42127
42415
|
const start = performance.now();
|
|
42128
|
-
const
|
|
42129
|
-
|
|
42416
|
+
const rangesToCompute = new RangeSet();
|
|
42417
|
+
rangesToCompute.addManyPositions(positions);
|
|
42130
42418
|
const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
|
|
42131
|
-
|
|
42132
|
-
|
|
42133
|
-
|
|
42134
|
-
this.evaluate(
|
|
42419
|
+
rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
|
|
42420
|
+
rangesToCompute.addMany(arrayFormulasPositions);
|
|
42421
|
+
rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
42422
|
+
this.evaluate(rangesToCompute);
|
|
42135
42423
|
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
42136
42424
|
}
|
|
42137
42425
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
42138
|
-
const
|
|
42426
|
+
const impactedRanges = new RangeSet();
|
|
42139
42427
|
for (const position of positions) {
|
|
42140
42428
|
const content = this.getters.getCell(position)?.content;
|
|
42141
42429
|
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
42142
42430
|
if (arrayFormulaPosition !== undefined) {
|
|
42143
42431
|
// take into account new collisions.
|
|
42144
|
-
|
|
42432
|
+
impactedRanges.addPosition(arrayFormulaPosition);
|
|
42145
42433
|
}
|
|
42146
42434
|
if (!content) {
|
|
42147
42435
|
// The previous content could have blocked some array formulas
|
|
42148
|
-
|
|
42436
|
+
impactedRanges.addPosition(position);
|
|
42149
42437
|
}
|
|
42150
42438
|
}
|
|
42151
|
-
const
|
|
42152
|
-
|
|
42153
|
-
for (const zone of zonesBySheetIds[sheetId]) {
|
|
42154
|
-
impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
42155
|
-
}
|
|
42439
|
+
for (const range of [...impactedRanges]) {
|
|
42440
|
+
impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
|
|
42156
42441
|
}
|
|
42157
|
-
return
|
|
42442
|
+
return impactedRanges;
|
|
42158
42443
|
}
|
|
42159
42444
|
buildDependencyGraph() {
|
|
42160
42445
|
this.blockedArrayFormulas = this.createEmptyPositionSet();
|
|
42161
42446
|
this.spreadingRelations = new SpreadingRelation();
|
|
42162
42447
|
this.formulaDependencies = lazy(() => {
|
|
42163
|
-
const
|
|
42164
|
-
|
|
42165
|
-
.
|
|
42166
|
-
|
|
42167
|
-
|
|
42168
|
-
|
|
42169
|
-
|
|
42170
|
-
|
|
42171
|
-
|
|
42172
|
-
|
|
42448
|
+
const rTreeItems = [];
|
|
42449
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
42450
|
+
const cells = this.getters.getCells(sheetId);
|
|
42451
|
+
for (const cellId in cells) {
|
|
42452
|
+
const cell = cells[cellId];
|
|
42453
|
+
if (cell.isFormula) {
|
|
42454
|
+
const directDependencies = cell.compiledFormula.dependencies;
|
|
42455
|
+
for (const range of directDependencies) {
|
|
42456
|
+
if (range.invalidSheetName || range.invalidXc) {
|
|
42457
|
+
continue;
|
|
42458
|
+
}
|
|
42459
|
+
rTreeItems.push({
|
|
42460
|
+
data: {
|
|
42461
|
+
sheetId,
|
|
42462
|
+
zone: positionToZone(this.getters.getCellPosition(cellId)),
|
|
42463
|
+
},
|
|
42464
|
+
boundingBox: { sheetId: range.sheetId, zone: range.zone },
|
|
42465
|
+
});
|
|
42466
|
+
}
|
|
42467
|
+
}
|
|
42468
|
+
}
|
|
42469
|
+
}
|
|
42470
|
+
return new FormulaDependencyGraph(rTreeItems);
|
|
42173
42471
|
});
|
|
42174
42472
|
}
|
|
42175
42473
|
evaluateAllCells() {
|
|
42176
42474
|
const start = performance.now();
|
|
42177
42475
|
this.evaluatedCells = new PositionMap();
|
|
42178
|
-
|
|
42476
|
+
const ranges = [];
|
|
42477
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
42478
|
+
const zone = this.getters.getSheetZone(sheetId);
|
|
42479
|
+
ranges.push({ sheetId, zone });
|
|
42480
|
+
}
|
|
42481
|
+
this.evaluate(ranges);
|
|
42179
42482
|
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
42180
42483
|
}
|
|
42181
42484
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
@@ -42199,48 +42502,47 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42199
42502
|
return handleError(error, "");
|
|
42200
42503
|
}
|
|
42201
42504
|
}
|
|
42202
|
-
getAllCells() {
|
|
42203
|
-
const positions = this.createEmptyPositionSet();
|
|
42204
|
-
positions.fillAllPositions();
|
|
42205
|
-
return positions;
|
|
42206
|
-
}
|
|
42207
42505
|
/**
|
|
42208
42506
|
* Return the position of formulas blocked by the given positions
|
|
42209
42507
|
* as well as all their dependencies.
|
|
42210
42508
|
*/
|
|
42211
42509
|
getArrayFormulasBlockedBy(sheetId, zone) {
|
|
42212
|
-
const arrayFormulaPositions =
|
|
42510
|
+
const arrayFormulaPositions = new RangeSet();
|
|
42213
42511
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
|
|
42214
|
-
arrayFormulaPositions.
|
|
42512
|
+
arrayFormulaPositions.addManyPositions(arrayFormulas);
|
|
42215
42513
|
const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
|
|
42216
42514
|
if (spilledPositions.length) {
|
|
42217
42515
|
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
42218
|
-
arrayFormulaPositions.
|
|
42516
|
+
arrayFormulaPositions.deleteManyPositions(spilledPositions);
|
|
42219
42517
|
}
|
|
42220
42518
|
arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
|
|
42221
42519
|
return arrayFormulaPositions;
|
|
42222
42520
|
}
|
|
42223
|
-
|
|
42521
|
+
nextRangesToUpdate = new RangeSet();
|
|
42224
42522
|
cellsBeingComputed = new Set();
|
|
42225
42523
|
symbolsBeingComputed = new Set();
|
|
42226
|
-
evaluate(
|
|
42524
|
+
evaluate(ranges) {
|
|
42227
42525
|
this.cellsBeingComputed = new Set();
|
|
42228
|
-
this.
|
|
42526
|
+
this.nextRangesToUpdate = new RangeSet(ranges);
|
|
42229
42527
|
let currentIteration = 0;
|
|
42230
|
-
while (!this.
|
|
42528
|
+
while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
|
|
42231
42529
|
this.updateCompilationParameters();
|
|
42232
|
-
const
|
|
42233
|
-
|
|
42234
|
-
|
|
42235
|
-
|
|
42236
|
-
|
|
42237
|
-
|
|
42238
|
-
|
|
42239
|
-
|
|
42240
|
-
|
|
42241
|
-
|
|
42242
|
-
|
|
42243
|
-
|
|
42530
|
+
const ranges = [...this.nextRangesToUpdate];
|
|
42531
|
+
this.nextRangesToUpdate.clear();
|
|
42532
|
+
this.clearEvaluatedRanges(ranges);
|
|
42533
|
+
for (const range of ranges) {
|
|
42534
|
+
const { left, bottom, right, top } = range.zone;
|
|
42535
|
+
for (let col = left; col <= right; col++) {
|
|
42536
|
+
for (let row = top; row <= bottom; row++) {
|
|
42537
|
+
const position = { sheetId: range.sheetId, col, row };
|
|
42538
|
+
if (this.nextRangesToUpdate.hasPosition(position)) {
|
|
42539
|
+
continue;
|
|
42540
|
+
}
|
|
42541
|
+
const evaluatedCell = this.computeCell(position);
|
|
42542
|
+
if (evaluatedCell !== EMPTY_CELL) {
|
|
42543
|
+
this.evaluatedCells.set(position, evaluatedCell);
|
|
42544
|
+
}
|
|
42545
|
+
}
|
|
42244
42546
|
}
|
|
42245
42547
|
}
|
|
42246
42548
|
onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
|
|
@@ -42249,6 +42551,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42249
42551
|
console.warn("Maximum iteration reached while evaluating cells");
|
|
42250
42552
|
}
|
|
42251
42553
|
}
|
|
42554
|
+
clearEvaluatedRanges(ranges) {
|
|
42555
|
+
for (const range of ranges) {
|
|
42556
|
+
const { left, bottom, right, top } = range.zone;
|
|
42557
|
+
for (let col = left; col <= right; col++) {
|
|
42558
|
+
for (let row = top; row <= bottom; row++) {
|
|
42559
|
+
this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
|
|
42560
|
+
}
|
|
42561
|
+
}
|
|
42562
|
+
}
|
|
42563
|
+
}
|
|
42252
42564
|
computeCell(position) {
|
|
42253
42565
|
const evaluation = this.evaluatedCells.get(position);
|
|
42254
42566
|
if (evaluation) {
|
|
@@ -42321,9 +42633,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42321
42633
|
}
|
|
42322
42634
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
42323
42635
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
42324
|
-
const invalidatedPositions = this.
|
|
42325
|
-
invalidatedPositions.delete({ sheetId,
|
|
42326
|
-
this.
|
|
42636
|
+
const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
42637
|
+
invalidatedPositions.delete({ sheetId, zone: resultZone });
|
|
42638
|
+
this.nextRangesToUpdate.addMany(invalidatedPositions);
|
|
42327
42639
|
}
|
|
42328
42640
|
assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
|
|
42329
42641
|
const numberOfCols = this.getters.getNumberCols(sheetId);
|
|
@@ -42398,7 +42710,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42398
42710
|
}
|
|
42399
42711
|
const sheetId = position.sheetId;
|
|
42400
42712
|
this.invalidatePositionsDependingOnSpread(sheetId, zone);
|
|
42401
|
-
this.
|
|
42713
|
+
this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
42402
42714
|
}
|
|
42403
42715
|
/**
|
|
42404
42716
|
* Wraps a GetSymbolValue function to add cycle detection
|
|
@@ -42433,13 +42745,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
42433
42745
|
}
|
|
42434
42746
|
return cell.compiledFormula.dependencies;
|
|
42435
42747
|
}
|
|
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);
|
|
42748
|
+
getCellsDependingOn(ranges) {
|
|
42749
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
|
|
42443
42750
|
}
|
|
42444
42751
|
}
|
|
42445
42752
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -46106,18 +46413,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46106
46413
|
return EMPTY_PIVOT_CELL;
|
|
46107
46414
|
}
|
|
46108
46415
|
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);
|
|
46416
|
+
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());
|
|
46417
|
+
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
|
|
46121
46418
|
const pivotCol = position.col - mainPosition.col;
|
|
46122
46419
|
const pivotRow = position.row - mainPosition.row;
|
|
46123
46420
|
return pivotCells[pivotCol][pivotRow];
|
|
@@ -55832,7 +56129,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
55832
56129
|
const startSnapshot = performance.now();
|
|
55833
56130
|
console.debug("Snapshot requested");
|
|
55834
56131
|
this.session.snapshot(this.exportData());
|
|
55835
|
-
this.garbageCollectExternalResources();
|
|
55836
56132
|
console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
|
|
55837
56133
|
}
|
|
55838
56134
|
console.debug("Model created in", performance.now() - start, "ms");
|
|
@@ -56218,11 +56514,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56218
56514
|
data = deepCopy$1(data);
|
|
56219
56515
|
return getXLSX(data);
|
|
56220
56516
|
}
|
|
56221
|
-
garbageCollectExternalResources() {
|
|
56222
|
-
for (const plugin of this.corePlugins) {
|
|
56223
|
-
plugin.garbageCollectExternalResources();
|
|
56224
|
-
}
|
|
56225
|
-
}
|
|
56226
56517
|
}
|
|
56227
56518
|
function createCommand(type, payload = {}) {
|
|
56228
56519
|
const command = deepCopy$1(payload);
|
|
@@ -65613,12 +65904,23 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65613
65904
|
.add("LinkEditor", LinkEditorPopoverBuilder)
|
|
65614
65905
|
.add("FilterMenu", FilterMenuPopoverBuilder);
|
|
65615
65906
|
|
|
65616
|
-
const
|
|
65617
|
-
|
|
65618
|
-
|
|
65619
|
-
|
|
65620
|
-
|
|
65621
|
-
|
|
65907
|
+
const DEFAULT_BAR_CHART_CONFIG = {
|
|
65908
|
+
type: "bar",
|
|
65909
|
+
title: {},
|
|
65910
|
+
dataSets: [],
|
|
65911
|
+
legendPosition: "none",
|
|
65912
|
+
dataSetsHaveTitle: false,
|
|
65913
|
+
stacked: false,
|
|
65914
|
+
};
|
|
65915
|
+
const DEFAULT_LINE_CHART_CONFIG = {
|
|
65916
|
+
type: "line",
|
|
65917
|
+
title: {},
|
|
65918
|
+
dataSets: [],
|
|
65919
|
+
legendPosition: "none",
|
|
65920
|
+
dataSetsHaveTitle: false,
|
|
65921
|
+
stacked: false,
|
|
65922
|
+
cumulative: false,
|
|
65923
|
+
labelsAsText: false,
|
|
65622
65924
|
};
|
|
65623
65925
|
function getUnboundRange(getters, zone) {
|
|
65624
65926
|
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
@@ -65657,43 +65959,19 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65657
65959
|
return detectedType;
|
|
65658
65960
|
}
|
|
65659
65961
|
function categorizeColumns(zones, getters) {
|
|
65660
|
-
const columns =
|
|
65661
|
-
number: [],
|
|
65662
|
-
text: [],
|
|
65663
|
-
date: [],
|
|
65664
|
-
};
|
|
65962
|
+
const columns = [];
|
|
65665
65963
|
for (const zone of getZonesByColumns(zones)) {
|
|
65666
65964
|
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
|
-
}
|
|
65965
|
+
columns.push({ zone, type: detectColumnType(cells) });
|
|
65672
65966
|
}
|
|
65673
65967
|
return columns;
|
|
65674
65968
|
}
|
|
65675
65969
|
function getCellStats(getters, zone) {
|
|
65676
65970
|
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
|
-
}
|
|
65971
|
+
const values = cells.map((c) => c.value?.toString().trim() || "").filter((s) => s);
|
|
65693
65972
|
return {
|
|
65694
|
-
uniqueCount:
|
|
65695
|
-
totalCount,
|
|
65696
|
-
percentageSum,
|
|
65973
|
+
uniqueCount: new Set(values).size,
|
|
65974
|
+
totalCount: values.length,
|
|
65697
65975
|
};
|
|
65698
65976
|
}
|
|
65699
65977
|
function isDatasetTitled(getters, column) {
|
|
@@ -65704,167 +65982,191 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65704
65982
|
});
|
|
65705
65983
|
return ![CellValueType.number, CellValueType.empty].includes(titleCell.type);
|
|
65706
65984
|
}
|
|
65707
|
-
|
|
65708
|
-
|
|
65709
|
-
|
|
65710
|
-
|
|
65711
|
-
|
|
65712
|
-
|
|
65713
|
-
|
|
65714
|
-
|
|
65715
|
-
}
|
|
65985
|
+
/**
|
|
65986
|
+
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
65987
|
+
* - If the column contains a single cell, create a scorecard.
|
|
65988
|
+
* - If the column type is "percentage", create a pie chart.
|
|
65989
|
+
* - If the column type is "text", create a pie chart
|
|
65990
|
+
* - If the column type is "date", create a line chart.
|
|
65991
|
+
* - Otherwise, create a bar chart.
|
|
65992
|
+
*/
|
|
65716
65993
|
function buildSingleColumnChart(column, getters) {
|
|
65717
65994
|
const { type, zone } = column;
|
|
65718
65995
|
const sheetId = getters.getActiveSheetId();
|
|
65719
65996
|
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
65720
65997
|
const dataRange = getUnboundRange(getters, zone);
|
|
65721
65998
|
const titleCell = getters.getEvaluatedCell({ sheetId, col: zone.left, row: zone.top });
|
|
65999
|
+
if (getZoneArea(zone) === 1) {
|
|
66000
|
+
return buildScorecard(zone, getters);
|
|
66001
|
+
}
|
|
65722
66002
|
switch (type) {
|
|
65723
66003
|
case "percentage":
|
|
65724
|
-
|
|
65725
|
-
|
|
66004
|
+
return {
|
|
66005
|
+
type: "pie",
|
|
65726
66006
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66007
|
+
dataSets: [{ dataRange }],
|
|
66008
|
+
legendPosition: "none",
|
|
65727
66009
|
dataSetsHaveTitle,
|
|
65728
|
-
|
|
65729
|
-
});
|
|
66010
|
+
};
|
|
65730
66011
|
case "text":
|
|
65731
66012
|
const cells = getters.getEvaluatedCellsInZone(sheetId, zone);
|
|
65732
66013
|
const titleCount = cells.reduce((count, cell) => (cell.value === titleCell.value ? count + 1 : count), 0);
|
|
65733
66014
|
const hasUniqueTitle = titleCell.value !== null && titleCount === 1;
|
|
65734
|
-
return
|
|
66015
|
+
return {
|
|
66016
|
+
type: "pie",
|
|
65735
66017
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
66018
|
+
dataSets: [{ dataRange }],
|
|
65736
66019
|
labelRange: dataRange,
|
|
65737
66020
|
dataSetsHaveTitle: hasUniqueTitle,
|
|
65738
|
-
isDoughnut: false,
|
|
65739
66021
|
aggregated: true,
|
|
65740
66022
|
legendPosition: "top",
|
|
65741
|
-
}
|
|
65742
|
-
// TODO: Handle date column with matrix chart when matrix chart is supported
|
|
66023
|
+
};
|
|
65743
66024
|
case "date":
|
|
65744
|
-
return
|
|
65745
|
-
|
|
66025
|
+
return {
|
|
66026
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66027
|
+
type: "line",
|
|
66028
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66029
|
+
dataSets: [{ dataRange }],
|
|
65746
66030
|
dataSetsHaveTitle,
|
|
65747
|
-
|
|
65748
|
-
labelsAsText: false,
|
|
65749
|
-
});
|
|
66031
|
+
};
|
|
65750
66032
|
}
|
|
65751
|
-
return
|
|
66033
|
+
return {
|
|
66034
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66035
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
66036
|
+
dataSets: [{ dataRange }],
|
|
66037
|
+
dataSetsHaveTitle,
|
|
66038
|
+
};
|
|
65752
66039
|
}
|
|
66040
|
+
/**
|
|
66041
|
+
* Builds a chart definition for a selection of two columns. The logic to detect the chart type always consider the
|
|
66042
|
+
* columns left to right, and is as follows:
|
|
66043
|
+
* - any type + percentage columns: pie chart
|
|
66044
|
+
* - number + number columns: scatter chart
|
|
66045
|
+
* - date + number columns: line chart
|
|
66046
|
+
* - text + number columns: treemap if repetition in labels
|
|
66047
|
+
* - any other combination: bar chart
|
|
66048
|
+
*/
|
|
65753
66049
|
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
|
-
});
|
|
66050
|
+
if (columns.length !== 2) {
|
|
66051
|
+
throw new Error("buildTwoColumnChart expects exactly two columns");
|
|
65761
66052
|
}
|
|
65762
|
-
|
|
65763
|
-
|
|
65764
|
-
|
|
65765
|
-
|
|
65766
|
-
|
|
65767
|
-
|
|
65768
|
-
|
|
66053
|
+
if (columns[1].type === "percentage") {
|
|
66054
|
+
return {
|
|
66055
|
+
type: "pie",
|
|
66056
|
+
title: {},
|
|
66057
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66058
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66059
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
66060
|
+
aggregated: true,
|
|
66061
|
+
legendPosition: "none",
|
|
66062
|
+
};
|
|
66063
|
+
}
|
|
66064
|
+
if (columns[0].type === "number" && columns[1].type === "number") {
|
|
66065
|
+
return {
|
|
66066
|
+
type: "scatter",
|
|
66067
|
+
title: {},
|
|
66068
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66069
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66070
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
65769
66071
|
labelsAsText: false,
|
|
65770
|
-
|
|
66072
|
+
legendPosition: "none",
|
|
66073
|
+
};
|
|
66074
|
+
}
|
|
66075
|
+
// TODO: Handle date + number with calendar chart when implemented (and change the docstring)
|
|
66076
|
+
if (columns[0].type === "date" && columns[1].type === "number") {
|
|
66077
|
+
return {
|
|
66078
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66079
|
+
type: "line",
|
|
66080
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66081
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66082
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[0]),
|
|
66083
|
+
};
|
|
65771
66084
|
}
|
|
65772
|
-
if (
|
|
65773
|
-
const
|
|
65774
|
-
const
|
|
66085
|
+
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
66086
|
+
const textColumn = columns[0];
|
|
66087
|
+
const numberColumn = columns[1];
|
|
65775
66088
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
65776
66089
|
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
66090
|
if (uniqueCount !== totalCount) {
|
|
65794
|
-
return
|
|
65795
|
-
|
|
66091
|
+
return {
|
|
66092
|
+
type: "treemap",
|
|
66093
|
+
title: {},
|
|
66094
|
+
dataSets: [{ dataRange: getUnboundRange(getters, textColumn.zone) }],
|
|
66095
|
+
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
65796
66096
|
dataSetsHaveTitle,
|
|
65797
|
-
|
|
66097
|
+
legendPosition: "none",
|
|
66098
|
+
};
|
|
65798
66099
|
}
|
|
65799
|
-
return createBaseChart("bar", [{ dataRange }], {
|
|
65800
|
-
labelRange,
|
|
65801
|
-
dataSetsHaveTitle,
|
|
65802
|
-
});
|
|
65803
66100
|
}
|
|
65804
|
-
|
|
65805
|
-
|
|
65806
|
-
|
|
65807
|
-
labelRange: getUnboundRange(getters,
|
|
65808
|
-
dataSetsHaveTitle: isDatasetTitled(getters,
|
|
65809
|
-
|
|
65810
|
-
labelsAsText: true,
|
|
65811
|
-
});
|
|
66101
|
+
return {
|
|
66102
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66103
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
66104
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
66105
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
66106
|
+
};
|
|
65812
66107
|
}
|
|
66108
|
+
/**
|
|
66109
|
+
* Builds a chart definition for a selection more than two columns. The logic to detect the chart type always consider
|
|
66110
|
+
* the columns left to right, and is as follows:
|
|
66111
|
+
* - multiple text + single number/percentage columns: sunburst if 3+ text columns, treemap otherwise
|
|
66112
|
+
* - any type + multiple percentage columns: pie chart
|
|
66113
|
+
* - date + multiple number columns: line chart
|
|
66114
|
+
* - any other combination: bar chart
|
|
66115
|
+
*/
|
|
65813
66116
|
function buildMultiColumnChart(columns, getters) {
|
|
65814
|
-
|
|
65815
|
-
|
|
65816
|
-
|
|
65817
|
-
|
|
65818
|
-
|
|
66117
|
+
if (columns.length < 3) {
|
|
66118
|
+
throw new Error("buildMultiColumnChart expects at least three columns");
|
|
66119
|
+
}
|
|
66120
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
66121
|
+
const lastColumn = columns[columns.length - 1];
|
|
66122
|
+
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
66123
|
+
if ((lastColumn.type === "percentage" || lastColumn.type === "number") &&
|
|
66124
|
+
columnsExceptLast.every((col) => col.type === "text")) {
|
|
66125
|
+
const dataSets = columnsExceptLast.map(({ zone }) => ({
|
|
65819
66126
|
dataRange: getUnboundRange(getters, zone),
|
|
65820
66127
|
}));
|
|
65821
|
-
return
|
|
65822
|
-
|
|
66128
|
+
return {
|
|
66129
|
+
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
66130
|
+
title: {},
|
|
66131
|
+
dataSets,
|
|
66132
|
+
labelRange: getUnboundRange(getters, lastColumn.zone),
|
|
65823
66133
|
dataSetsHaveTitle,
|
|
65824
|
-
|
|
66134
|
+
legendPosition: "none",
|
|
66135
|
+
};
|
|
65825
66136
|
}
|
|
65826
|
-
const
|
|
66137
|
+
const firstColumn = columns[0];
|
|
66138
|
+
const columnsExceptFirst = columns.slice(1);
|
|
66139
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }) => ({
|
|
65827
66140
|
dataRange: getUnboundRange(getters, zone),
|
|
65828
66141
|
}));
|
|
65829
|
-
if (
|
|
65830
|
-
return
|
|
65831
|
-
|
|
66142
|
+
if (columnsExceptFirst.every((col) => col.type === "percentage")) {
|
|
66143
|
+
return {
|
|
66144
|
+
type: "pie",
|
|
66145
|
+
title: {},
|
|
66146
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66147
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
65832
66148
|
dataSetsHaveTitle,
|
|
65833
|
-
|
|
65834
|
-
labelsAsText: false,
|
|
66149
|
+
aggregated: false,
|
|
65835
66150
|
legendPosition: "top",
|
|
65836
|
-
}
|
|
66151
|
+
};
|
|
65837
66152
|
}
|
|
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
|
-
}
|
|
66153
|
+
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) {
|
|
66154
|
+
return {
|
|
66155
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
66156
|
+
type: "line",
|
|
66157
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66158
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
66159
|
+
dataSetsHaveTitle,
|
|
66160
|
+
legendPosition: "top",
|
|
66161
|
+
};
|
|
65860
66162
|
}
|
|
65861
|
-
|
|
65862
|
-
|
|
65863
|
-
|
|
66163
|
+
return {
|
|
66164
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
66165
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
66166
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
65864
66167
|
dataSetsHaveTitle,
|
|
65865
|
-
aggregated: true,
|
|
65866
66168
|
legendPosition: "top",
|
|
65867
|
-
}
|
|
66169
|
+
};
|
|
65868
66170
|
}
|
|
65869
66171
|
function buildScorecard(zone, getters) {
|
|
65870
66172
|
const cell = getters.getCell({
|
|
@@ -65887,22 +66189,18 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65887
66189
|
*/
|
|
65888
66190
|
function getSmartChartDefinition(zones, getters) {
|
|
65889
66191
|
const columns = categorizeColumns(zones, getters);
|
|
65890
|
-
|
|
65891
|
-
|
|
65892
|
-
|
|
65893
|
-
|
|
65894
|
-
|
|
65895
|
-
|
|
65896
|
-
});
|
|
66192
|
+
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
66193
|
+
const dataSets = columns.map(({ zone }) => ({ dataRange: getUnboundRange(getters, zone) }));
|
|
66194
|
+
return { ...DEFAULT_BAR_CHART_CONFIG, dataSets };
|
|
66195
|
+
}
|
|
66196
|
+
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
66197
|
+
switch (nonEmptyColumns.length) {
|
|
65897
66198
|
case 1:
|
|
65898
|
-
|
|
65899
|
-
return getZoneArea(singleColumn.zone) === 1
|
|
65900
|
-
? buildScorecard(singleColumn.zone, getters)
|
|
65901
|
-
: buildSingleColumnChart(singleColumn, getters);
|
|
66199
|
+
return buildSingleColumnChart(nonEmptyColumns[0], getters);
|
|
65902
66200
|
case 2:
|
|
65903
|
-
return buildTwoColumnChart(
|
|
66201
|
+
return buildTwoColumnChart(nonEmptyColumns, getters);
|
|
65904
66202
|
default:
|
|
65905
|
-
return buildMultiColumnChart(
|
|
66203
|
+
return buildMultiColumnChart(nonEmptyColumns, getters);
|
|
65906
66204
|
}
|
|
65907
66205
|
}
|
|
65908
66206
|
|
|
@@ -66435,23 +66733,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
66435
66733
|
//------------------------------------------------------------------------------
|
|
66436
66734
|
// Image
|
|
66437
66735
|
//------------------------------------------------------------------------------
|
|
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
66736
|
const CREATE_IMAGE = async (env) => {
|
|
66448
66737
|
if (env.imageProvider) {
|
|
66449
66738
|
const sheetId = env.model.getters.getActiveSheetId();
|
|
66450
66739
|
const figureId = env.model.uuidGenerator.smallUuid();
|
|
66451
|
-
const image = await requestImage(
|
|
66452
|
-
if (!image) {
|
|
66453
|
-
return;
|
|
66454
|
-
}
|
|
66740
|
+
const image = await env.imageProvider.requestImage();
|
|
66455
66741
|
const size = getMaxFigureSize(env.model.getters, image.size);
|
|
66456
66742
|
const { col, row, offset } = centerFigurePosition(env.model.getters, size);
|
|
66457
66743
|
env.model.dispatch("CREATE_IMAGE", {
|
|
@@ -78050,16 +78336,18 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78050
78336
|
|
|
78051
78337
|
class PivotSidePanelStore extends SpreadsheetStore {
|
|
78052
78338
|
pivotId;
|
|
78339
|
+
updateMode;
|
|
78053
78340
|
mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
|
|
78054
|
-
|
|
78341
|
+
_updatesAreDeferred;
|
|
78055
78342
|
draft = null;
|
|
78056
78343
|
notification = this.get(NotificationStore);
|
|
78057
78344
|
alreadyNotified = false;
|
|
78058
78345
|
alreadyNotifiedForPivotSize = false;
|
|
78059
|
-
constructor(get, pivotId) {
|
|
78346
|
+
constructor(get, pivotId, updateMode = "canDefer") {
|
|
78060
78347
|
super(get);
|
|
78061
78348
|
this.pivotId = pivotId;
|
|
78062
|
-
this.
|
|
78349
|
+
this.updateMode = updateMode;
|
|
78350
|
+
this._updatesAreDeferred =
|
|
78063
78351
|
this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
|
|
78064
78352
|
}
|
|
78065
78353
|
handle(cmd) {
|
|
@@ -78070,6 +78358,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78070
78358
|
}
|
|
78071
78359
|
}
|
|
78072
78360
|
}
|
|
78361
|
+
get updatesAreDeferred() {
|
|
78362
|
+
return this.updateMode === "neverDefer" ? false : this._updatesAreDeferred;
|
|
78363
|
+
}
|
|
78073
78364
|
get fields() {
|
|
78074
78365
|
return this.pivot.getFields();
|
|
78075
78366
|
}
|
|
@@ -78144,7 +78435,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78144
78435
|
}
|
|
78145
78436
|
reset(pivotId) {
|
|
78146
78437
|
this.pivotId = pivotId;
|
|
78147
|
-
this.
|
|
78438
|
+
this._updatesAreDeferred = true;
|
|
78148
78439
|
this.draft = null;
|
|
78149
78440
|
}
|
|
78150
78441
|
deferUpdates(shouldDefer) {
|
|
@@ -78155,7 +78446,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78155
78446
|
else {
|
|
78156
78447
|
this.update({ deferUpdates: shouldDefer });
|
|
78157
78448
|
}
|
|
78158
|
-
this.
|
|
78449
|
+
this._updatesAreDeferred = shouldDefer;
|
|
78159
78450
|
}
|
|
78160
78451
|
applyUpdate() {
|
|
78161
78452
|
if (this.draft) {
|
|
@@ -78409,6 +78700,26 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78409
78700
|
editor: PivotSpreadsheetSidePanel,
|
|
78410
78701
|
});
|
|
78411
78702
|
|
|
78703
|
+
class PivotDesignPanel extends owl.Component {
|
|
78704
|
+
static template = "o-spreadsheet-PivotDesignPanel";
|
|
78705
|
+
static props = { pivotId: String };
|
|
78706
|
+
static components = { Section, Checkbox };
|
|
78707
|
+
store;
|
|
78708
|
+
setup() {
|
|
78709
|
+
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId, "neverDefer");
|
|
78710
|
+
}
|
|
78711
|
+
updatePivotStyleProperty(key, value) {
|
|
78712
|
+
this.store.update({ style: { ...this.pivotStyle, [key]: value } });
|
|
78713
|
+
}
|
|
78714
|
+
get pivotStyle() {
|
|
78715
|
+
const pivot = this.env.model.getters.getPivotCoreDefinition(this.props.pivotId);
|
|
78716
|
+
return pivot.style || {};
|
|
78717
|
+
}
|
|
78718
|
+
get defaultStyle() {
|
|
78719
|
+
return DEFAULT_PIVOT_STYLE;
|
|
78720
|
+
}
|
|
78721
|
+
}
|
|
78722
|
+
|
|
78412
78723
|
class PivotSidePanel extends owl.Component {
|
|
78413
78724
|
static template = "o-spreadsheet-PivotSidePanel";
|
|
78414
78725
|
static props = {
|
|
@@ -78418,6 +78729,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78418
78729
|
static components = {
|
|
78419
78730
|
PivotLayoutConfigurator,
|
|
78420
78731
|
Section,
|
|
78732
|
+
PivotDesignPanel,
|
|
78733
|
+
};
|
|
78734
|
+
state = owl.useState({ panel: "configuration" });
|
|
78735
|
+
panelContentRef = owl.useRef("panelContent");
|
|
78736
|
+
scrollPositions = {
|
|
78737
|
+
configuration: 0,
|
|
78738
|
+
design: 0,
|
|
78421
78739
|
};
|
|
78422
78740
|
setup() {
|
|
78423
78741
|
useHighlights(this);
|
|
@@ -78432,6 +78750,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
78432
78750
|
get highlights() {
|
|
78433
78751
|
return getPivotHighlights(this.env.model.getters, this.props.pivotId);
|
|
78434
78752
|
}
|
|
78753
|
+
switchPanel(panel) {
|
|
78754
|
+
const el = this.panelContentRef.el;
|
|
78755
|
+
if (el) {
|
|
78756
|
+
this.scrollPositions[this.state.panel] = el.scrollTop;
|
|
78757
|
+
}
|
|
78758
|
+
this.state.panel = panel;
|
|
78759
|
+
}
|
|
78435
78760
|
}
|
|
78436
78761
|
|
|
78437
78762
|
class RemoveDuplicatesPanel extends owl.Component {
|
|
@@ -87581,9 +87906,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
87581
87906
|
exports.tokenize = tokenize;
|
|
87582
87907
|
|
|
87583
87908
|
|
|
87584
|
-
__info__.version = "19.1.0-alpha.
|
|
87585
|
-
__info__.date = "2025-10-
|
|
87586
|
-
__info__.hash = "
|
|
87909
|
+
__info__.version = "19.1.0-alpha.8";
|
|
87910
|
+
__info__.date = "2025-10-23T08:20:05.310Z";
|
|
87911
|
+
__info__.hash = "78717d4";
|
|
87587
87912
|
|
|
87588
87913
|
|
|
87589
87914
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|