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