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