@odoo/o-spreadsheet 16.4.15 → 16.4.17
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.cjs.js +921 -188
- package/dist/o-spreadsheet.d.ts +19 -19
- package/dist/o-spreadsheet.esm.js +921 -188
- package/dist/o-spreadsheet.iife.js +921 -188
- package/dist/o-spreadsheet.iife.min.js +264 -264
- package/dist/o_spreadsheet.xml +3 -3
- package/package.json +4 -2
|
@@ -298,13 +298,6 @@
|
|
|
298
298
|
const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
|
|
299
299
|
|
|
300
300
|
//------------------------------------------------------------------------------
|
|
301
|
-
/**
|
|
302
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
303
|
-
* is ordered.
|
|
304
|
-
*/
|
|
305
|
-
function stringify(obj) {
|
|
306
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
307
|
-
}
|
|
308
301
|
/**
|
|
309
302
|
* Remove quotes from a quoted string
|
|
310
303
|
* ```js
|
|
@@ -524,7 +517,7 @@
|
|
|
524
517
|
*/
|
|
525
518
|
function getItemId(item, itemsDic) {
|
|
526
519
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
527
|
-
if (
|
|
520
|
+
if (deepEquals(value, item)) {
|
|
528
521
|
return parseInt(key, 10);
|
|
529
522
|
}
|
|
530
523
|
}
|
|
@@ -2342,8 +2335,10 @@
|
|
|
2342
2335
|
const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
|
|
2343
2336
|
// Same as above, but matches the exact string (nothing before or after)
|
|
2344
2337
|
const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
|
|
2345
|
-
/** Reference of a column header (eg. A, AB) */
|
|
2346
|
-
const colHeader = new RegExp(
|
|
2338
|
+
/** Reference of a column header (eg. A, AB, $A) */
|
|
2339
|
+
const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
|
|
2340
|
+
/** Reference of a row header (eg. 1, $1) */
|
|
2341
|
+
const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
|
|
2347
2342
|
/** Reference of a column (eg. A, $CA, Sheet1!B) */
|
|
2348
2343
|
const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
|
|
2349
2344
|
/** Reference of a row (eg. 1, 59, Sheet1!9) */
|
|
@@ -2373,6 +2368,9 @@
|
|
|
2373
2368
|
function isColHeader(str) {
|
|
2374
2369
|
return colHeader.test(str);
|
|
2375
2370
|
}
|
|
2371
|
+
function isRowHeader(str) {
|
|
2372
|
+
return rowHeader.test(str);
|
|
2373
|
+
}
|
|
2376
2374
|
/**
|
|
2377
2375
|
* Return true if the given xc is the reference of a single cell,
|
|
2378
2376
|
* without any specified sheet (e.g. A1)
|
|
@@ -3045,18 +3043,10 @@
|
|
|
3045
3043
|
if (isFullCol) {
|
|
3046
3044
|
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
3047
3045
|
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
3048
|
-
if (zone.left === zone.right) {
|
|
3049
|
-
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
3050
|
-
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
3051
|
-
}
|
|
3052
3046
|
}
|
|
3053
3047
|
if (isFullRow) {
|
|
3054
3048
|
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
3055
3049
|
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
3056
|
-
if (zone.top === zone.bottom) {
|
|
3057
|
-
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
3058
|
-
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
3059
|
-
}
|
|
3060
3050
|
}
|
|
3061
3051
|
return parts;
|
|
3062
3052
|
}
|
|
@@ -5608,6 +5598,7 @@
|
|
|
5608
5598
|
},
|
|
5609
5599
|
};
|
|
5610
5600
|
|
|
5601
|
+
const macRegex = /Mac/i;
|
|
5611
5602
|
/**
|
|
5612
5603
|
* Return true if the event was triggered from
|
|
5613
5604
|
* a child element.
|
|
@@ -5645,6 +5636,17 @@
|
|
|
5645
5636
|
}
|
|
5646
5637
|
function getOpenedMenus() {
|
|
5647
5638
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
5639
|
+
}
|
|
5640
|
+
function isMacOS() {
|
|
5641
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
5645
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
5646
|
+
* On Mac, this is the "meta" or "command" key.
|
|
5647
|
+
*/
|
|
5648
|
+
function isCtrlKey(ev) {
|
|
5649
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
5648
5650
|
}
|
|
5649
5651
|
|
|
5650
5652
|
/**
|
|
@@ -14701,7 +14703,7 @@
|
|
|
14701
14703
|
SPACE: goTo(State.RightRef),
|
|
14702
14704
|
NUMBER: goTo(State.Found),
|
|
14703
14705
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
14704
|
-
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
|
|
14706
|
+
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
|
|
14705
14707
|
},
|
|
14706
14708
|
[State.RightColumnRef]: {
|
|
14707
14709
|
SPACE: goTo(State.RightColumnRef),
|
|
@@ -14712,6 +14714,7 @@
|
|
|
14712
14714
|
SPACE: goTo(State.RightRowRef),
|
|
14713
14715
|
NUMBER: goTo(State.Found),
|
|
14714
14716
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
14717
|
+
SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
|
|
14715
14718
|
},
|
|
14716
14719
|
[State.Found]: {},
|
|
14717
14720
|
};
|
|
@@ -16869,7 +16872,7 @@
|
|
|
16869
16872
|
});
|
|
16870
16873
|
return {
|
|
16871
16874
|
chartJsConfig: config,
|
|
16872
|
-
background: getters.
|
|
16875
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
16873
16876
|
};
|
|
16874
16877
|
}
|
|
16875
16878
|
|
|
@@ -17593,7 +17596,7 @@
|
|
|
17593
17596
|
};
|
|
17594
17597
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
17595
17598
|
}
|
|
17596
|
-
const background = getters.
|
|
17599
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
17597
17600
|
const locale = getters.getLocale();
|
|
17598
17601
|
return {
|
|
17599
17602
|
title: _t(chart.title),
|
|
@@ -17602,7 +17605,7 @@
|
|
|
17602
17605
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
17603
17606
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
17604
17607
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
17605
|
-
fontColor
|
|
17608
|
+
fontColor,
|
|
17606
17609
|
background,
|
|
17607
17610
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
17608
17611
|
? getters.getCellStyle({
|
|
@@ -20497,10 +20500,10 @@
|
|
|
20497
20500
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
20498
20501
|
const direction = arrowMap[ev.key];
|
|
20499
20502
|
if (ev.shiftKey) {
|
|
20500
|
-
selection.resizeAnchorZone(direction, ev
|
|
20503
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
20501
20504
|
}
|
|
20502
20505
|
else {
|
|
20503
|
-
selection.moveAnchorCell(direction, ev
|
|
20506
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
20504
20507
|
}
|
|
20505
20508
|
}
|
|
20506
20509
|
|
|
@@ -25545,7 +25548,10 @@
|
|
|
25545
25548
|
return;
|
|
25546
25549
|
}
|
|
25547
25550
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
25548
|
-
this.props.onCellClicked(col, row, {
|
|
25551
|
+
this.props.onCellClicked(col, row, {
|
|
25552
|
+
expandZone: ev.shiftKey,
|
|
25553
|
+
addZone: isCtrlKey(ev),
|
|
25554
|
+
});
|
|
25549
25555
|
}
|
|
25550
25556
|
onDoubleClick(ev) {
|
|
25551
25557
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -25618,6 +25624,7 @@
|
|
|
25618
25624
|
draggerShadowThickness: 0,
|
|
25619
25625
|
delta: 0,
|
|
25620
25626
|
base: 0,
|
|
25627
|
+
position: "before",
|
|
25621
25628
|
});
|
|
25622
25629
|
_computeHandleDisplay(ev) {
|
|
25623
25630
|
const position = this._getEvOffset(ev);
|
|
@@ -25742,11 +25749,13 @@
|
|
|
25742
25749
|
this.state.draggerLinePosition = dimensions.start;
|
|
25743
25750
|
this.state.draggerShadowPosition = dimensions.start;
|
|
25744
25751
|
this.state.base = elementIndex;
|
|
25752
|
+
this.state.position = "before";
|
|
25745
25753
|
}
|
|
25746
25754
|
else if (this._getSelectedZoneEnd() < elementIndex) {
|
|
25747
25755
|
this.state.draggerLinePosition = dimensions.end;
|
|
25748
25756
|
this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
|
|
25749
|
-
this.state.base = elementIndex
|
|
25757
|
+
this.state.base = elementIndex;
|
|
25758
|
+
this.state.position = "after";
|
|
25750
25759
|
}
|
|
25751
25760
|
else {
|
|
25752
25761
|
this.state.draggerLinePosition = startDimensions.start;
|
|
@@ -25770,7 +25779,7 @@
|
|
|
25770
25779
|
this._increaseSelection(index);
|
|
25771
25780
|
}
|
|
25772
25781
|
else {
|
|
25773
|
-
this._selectElement(index, ev
|
|
25782
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
25774
25783
|
}
|
|
25775
25784
|
this.lastSelectedElementIndex = index;
|
|
25776
25785
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -25783,7 +25792,7 @@
|
|
|
25783
25792
|
const mouseUpSelect = () => {
|
|
25784
25793
|
this.state.isSelecting = false;
|
|
25785
25794
|
this.lastSelectedElementIndex = null;
|
|
25786
|
-
this.env.model.dispatch(ev
|
|
25795
|
+
this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
|
|
25787
25796
|
this._computeGrabDisplay(ev);
|
|
25788
25797
|
};
|
|
25789
25798
|
dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
|
|
@@ -25929,13 +25938,14 @@
|
|
|
25929
25938
|
dimension: "COL",
|
|
25930
25939
|
base: this.state.base,
|
|
25931
25940
|
elements,
|
|
25941
|
+
position: this.state.position,
|
|
25932
25942
|
});
|
|
25933
25943
|
if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
|
|
25934
25944
|
this.env.raiseError(MergeErrorMessage);
|
|
25935
25945
|
}
|
|
25936
25946
|
}
|
|
25937
|
-
_selectElement(index,
|
|
25938
|
-
this.env.model.selection.selectColumn(index,
|
|
25947
|
+
_selectElement(index, addDistinctHeader) {
|
|
25948
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
25939
25949
|
}
|
|
25940
25950
|
_increaseSelection(index) {
|
|
25941
25951
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -26102,13 +26112,14 @@
|
|
|
26102
26112
|
dimension: "ROW",
|
|
26103
26113
|
base: this.state.base,
|
|
26104
26114
|
elements,
|
|
26115
|
+
position: this.state.position,
|
|
26105
26116
|
});
|
|
26106
26117
|
if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
|
|
26107
26118
|
this.env.raiseError(MergeErrorMessage);
|
|
26108
26119
|
}
|
|
26109
26120
|
}
|
|
26110
|
-
_selectElement(index,
|
|
26111
|
-
this.env.model.selection.selectRow(index,
|
|
26121
|
+
_selectElement(index, addDistinctHeader) {
|
|
26122
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
26112
26123
|
}
|
|
26113
26124
|
_increaseSelection(index) {
|
|
26114
26125
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -26209,8 +26220,8 @@
|
|
|
26209
26220
|
return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
|
|
26210
26221
|
}
|
|
26211
26222
|
const onMouseWheel = (ev) => {
|
|
26212
|
-
const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
26213
|
-
const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
26223
|
+
const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
26224
|
+
const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
26214
26225
|
handler(deltaX, deltaY);
|
|
26215
26226
|
};
|
|
26216
26227
|
return onMouseWheel;
|
|
@@ -26914,8 +26925,8 @@
|
|
|
26914
26925
|
// ---------------------------------------------------------------------------
|
|
26915
26926
|
// Zone selection with mouse
|
|
26916
26927
|
// ---------------------------------------------------------------------------
|
|
26917
|
-
onCellClicked(col, row, {
|
|
26918
|
-
if (
|
|
26928
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
26929
|
+
if (addZone) {
|
|
26919
26930
|
this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
|
|
26920
26931
|
}
|
|
26921
26932
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
@@ -26924,10 +26935,10 @@
|
|
|
26924
26935
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
26925
26936
|
this.env.model.dispatch("STOP_EDITION");
|
|
26926
26937
|
}
|
|
26927
|
-
if (
|
|
26938
|
+
if (expandZone) {
|
|
26928
26939
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
26929
26940
|
}
|
|
26930
|
-
else if (
|
|
26941
|
+
else if (addZone) {
|
|
26931
26942
|
this.env.model.selection.addCellToSelection(col, row);
|
|
26932
26943
|
}
|
|
26933
26944
|
else {
|
|
@@ -26989,9 +27000,7 @@
|
|
|
26989
27000
|
return;
|
|
26990
27001
|
}
|
|
26991
27002
|
let keyDownString = "";
|
|
26992
|
-
if (ev
|
|
26993
|
-
keyDownString += "CTRL+";
|
|
26994
|
-
if (ev.metaKey)
|
|
27003
|
+
if (isCtrlKey(ev))
|
|
26995
27004
|
keyDownString += "CTRL+";
|
|
26996
27005
|
if (ev.altKey)
|
|
26997
27006
|
keyDownString += "ALT+";
|
|
@@ -31639,7 +31648,7 @@
|
|
|
31639
31648
|
*/
|
|
31640
31649
|
function getBorderId(border) {
|
|
31641
31650
|
for (let [key, value] of Object.entries(borders)) {
|
|
31642
|
-
if (
|
|
31651
|
+
if (deepEquals(value, border)) {
|
|
31643
31652
|
return parseInt(key, 10);
|
|
31644
31653
|
}
|
|
31645
31654
|
}
|
|
@@ -34548,7 +34557,7 @@
|
|
|
34548
34557
|
const elements = cmd.dimension === "COL"
|
|
34549
34558
|
? this.getNumberCols(cmd.sheetId)
|
|
34550
34559
|
: this.getNumberRows(cmd.sheetId);
|
|
34551
|
-
if (cmd.base < 0 || cmd.base
|
|
34560
|
+
if (cmd.base < 0 || cmd.base >= elements) {
|
|
34552
34561
|
return 88 /* CommandResult.InvalidHeaderIndex */;
|
|
34553
34562
|
}
|
|
34554
34563
|
else if (cmd.quantity <= 0) {
|
|
@@ -35489,10 +35498,13 @@
|
|
|
35489
35498
|
if (evaluatedCell === undefined) {
|
|
35490
35499
|
return { value: null, format: this.getters.getCell(position)?.format };
|
|
35491
35500
|
}
|
|
35501
|
+
if (evaluatedCell.type === CellValueType.error) {
|
|
35502
|
+
throw evaluatedCell.error;
|
|
35503
|
+
}
|
|
35492
35504
|
return evaluatedCell;
|
|
35493
35505
|
}
|
|
35494
35506
|
getEvaluatedCellIfNotEmpty(position) {
|
|
35495
|
-
const evaluatedCell = this.
|
|
35507
|
+
const evaluatedCell = this.computeCell(position);
|
|
35496
35508
|
if (evaluatedCell.type === CellValueType.empty) {
|
|
35497
35509
|
const cell = this.getters.getCell(position);
|
|
35498
35510
|
if (!cell || (!cell.isFormula && cell.content === "")) {
|
|
@@ -35501,13 +35513,6 @@
|
|
|
35501
35513
|
}
|
|
35502
35514
|
return evaluatedCell;
|
|
35503
35515
|
}
|
|
35504
|
-
getEvaluatedCell(position) {
|
|
35505
|
-
const evaluatedCell = this.computeCell(position);
|
|
35506
|
-
if (evaluatedCell.type === CellValueType.error) {
|
|
35507
|
-
throw evaluatedCell.error;
|
|
35508
|
-
}
|
|
35509
|
-
return evaluatedCell;
|
|
35510
|
-
}
|
|
35511
35516
|
/**
|
|
35512
35517
|
* Return the values of the cell(s) used in reference, but always in the format of a range even
|
|
35513
35518
|
* if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
|
|
@@ -35530,7 +35535,11 @@
|
|
|
35530
35535
|
const { top, left, bottom, right } = zone;
|
|
35531
35536
|
const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
|
|
35532
35537
|
if (cacheKey in this.rangeCache) {
|
|
35533
|
-
|
|
35538
|
+
const result = this.rangeCache[cacheKey];
|
|
35539
|
+
if (result instanceof EvaluationError) {
|
|
35540
|
+
throw result;
|
|
35541
|
+
}
|
|
35542
|
+
return result;
|
|
35534
35543
|
}
|
|
35535
35544
|
const height = _zone.bottom - _zone.top + 1;
|
|
35536
35545
|
const width = _zone.right - _zone.left + 1;
|
|
@@ -35543,6 +35552,10 @@
|
|
|
35543
35552
|
format[colIndex] = new Array(height);
|
|
35544
35553
|
for (let row = _zone.top; row <= _zone.bottom; row++) {
|
|
35545
35554
|
const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
|
|
35555
|
+
if (evaluatedCell?.type === CellValueType.error) {
|
|
35556
|
+
this.rangeCache[cacheKey] = evaluatedCell.error;
|
|
35557
|
+
throw evaluatedCell.error;
|
|
35558
|
+
}
|
|
35546
35559
|
const rowIndex = row - _zone.top;
|
|
35547
35560
|
value[colIndex][rowIndex] = evaluatedCell?.value;
|
|
35548
35561
|
if (evaluatedCell?.format !== undefined) {
|
|
@@ -35556,56 +35569,758 @@
|
|
|
35556
35569
|
}
|
|
35557
35570
|
}
|
|
35558
35571
|
|
|
35572
|
+
function quickselect(arr, k, left, right, compare) {
|
|
35573
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
35574
|
+
}
|
|
35575
|
+
|
|
35576
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
35577
|
+
|
|
35578
|
+
while (right > left) {
|
|
35579
|
+
if (right - left > 600) {
|
|
35580
|
+
var n = right - left + 1;
|
|
35581
|
+
var m = k - left + 1;
|
|
35582
|
+
var z = Math.log(n);
|
|
35583
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
35584
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
35585
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
35586
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
35587
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
35588
|
+
}
|
|
35589
|
+
|
|
35590
|
+
var t = arr[k];
|
|
35591
|
+
var i = left;
|
|
35592
|
+
var j = right;
|
|
35593
|
+
|
|
35594
|
+
swap(arr, left, k);
|
|
35595
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
35596
|
+
|
|
35597
|
+
while (i < j) {
|
|
35598
|
+
swap(arr, i, j);
|
|
35599
|
+
i++;
|
|
35600
|
+
j--;
|
|
35601
|
+
while (compare(arr[i], t) < 0) i++;
|
|
35602
|
+
while (compare(arr[j], t) > 0) j--;
|
|
35603
|
+
}
|
|
35604
|
+
|
|
35605
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
35606
|
+
else {
|
|
35607
|
+
j++;
|
|
35608
|
+
swap(arr, j, right);
|
|
35609
|
+
}
|
|
35610
|
+
|
|
35611
|
+
if (j <= k) left = j + 1;
|
|
35612
|
+
if (k <= j) right = j - 1;
|
|
35613
|
+
}
|
|
35614
|
+
}
|
|
35615
|
+
|
|
35616
|
+
function swap(arr, i, j) {
|
|
35617
|
+
var tmp = arr[i];
|
|
35618
|
+
arr[i] = arr[j];
|
|
35619
|
+
arr[j] = tmp;
|
|
35620
|
+
}
|
|
35621
|
+
|
|
35622
|
+
function defaultCompare(a, b) {
|
|
35623
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
35624
|
+
}
|
|
35625
|
+
|
|
35626
|
+
class RBush {
|
|
35627
|
+
constructor(maxEntries = 9) {
|
|
35628
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
35629
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
35630
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
35631
|
+
this.clear();
|
|
35632
|
+
}
|
|
35633
|
+
|
|
35634
|
+
all() {
|
|
35635
|
+
return this._all(this.data, []);
|
|
35636
|
+
}
|
|
35637
|
+
|
|
35638
|
+
search(bbox) {
|
|
35639
|
+
let node = this.data;
|
|
35640
|
+
const result = [];
|
|
35641
|
+
|
|
35642
|
+
if (!intersects(bbox, node)) return result;
|
|
35643
|
+
|
|
35644
|
+
const toBBox = this.toBBox;
|
|
35645
|
+
const nodesToSearch = [];
|
|
35646
|
+
|
|
35647
|
+
while (node) {
|
|
35648
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35649
|
+
const child = node.children[i];
|
|
35650
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
35651
|
+
|
|
35652
|
+
if (intersects(bbox, childBBox)) {
|
|
35653
|
+
if (node.leaf) result.push(child);
|
|
35654
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
35655
|
+
else nodesToSearch.push(child);
|
|
35656
|
+
}
|
|
35657
|
+
}
|
|
35658
|
+
node = nodesToSearch.pop();
|
|
35659
|
+
}
|
|
35660
|
+
|
|
35661
|
+
return result;
|
|
35662
|
+
}
|
|
35663
|
+
|
|
35664
|
+
collides(bbox) {
|
|
35665
|
+
let node = this.data;
|
|
35666
|
+
|
|
35667
|
+
if (!intersects(bbox, node)) return false;
|
|
35668
|
+
|
|
35669
|
+
const nodesToSearch = [];
|
|
35670
|
+
while (node) {
|
|
35671
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35672
|
+
const child = node.children[i];
|
|
35673
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
35674
|
+
|
|
35675
|
+
if (intersects(bbox, childBBox)) {
|
|
35676
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
35677
|
+
nodesToSearch.push(child);
|
|
35678
|
+
}
|
|
35679
|
+
}
|
|
35680
|
+
node = nodesToSearch.pop();
|
|
35681
|
+
}
|
|
35682
|
+
|
|
35683
|
+
return false;
|
|
35684
|
+
}
|
|
35685
|
+
|
|
35686
|
+
load(data) {
|
|
35687
|
+
if (!(data && data.length)) return this;
|
|
35688
|
+
|
|
35689
|
+
if (data.length < this._minEntries) {
|
|
35690
|
+
for (let i = 0; i < data.length; i++) {
|
|
35691
|
+
this.insert(data[i]);
|
|
35692
|
+
}
|
|
35693
|
+
return this;
|
|
35694
|
+
}
|
|
35695
|
+
|
|
35696
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
35697
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
35698
|
+
|
|
35699
|
+
if (!this.data.children.length) {
|
|
35700
|
+
// save as is if tree is empty
|
|
35701
|
+
this.data = node;
|
|
35702
|
+
|
|
35703
|
+
} else if (this.data.height === node.height) {
|
|
35704
|
+
// split root if trees have the same height
|
|
35705
|
+
this._splitRoot(this.data, node);
|
|
35706
|
+
|
|
35707
|
+
} else {
|
|
35708
|
+
if (this.data.height < node.height) {
|
|
35709
|
+
// swap trees if inserted one is bigger
|
|
35710
|
+
const tmpNode = this.data;
|
|
35711
|
+
this.data = node;
|
|
35712
|
+
node = tmpNode;
|
|
35713
|
+
}
|
|
35714
|
+
|
|
35715
|
+
// insert the small tree into the large tree at appropriate level
|
|
35716
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
35717
|
+
}
|
|
35718
|
+
|
|
35719
|
+
return this;
|
|
35720
|
+
}
|
|
35721
|
+
|
|
35722
|
+
insert(item) {
|
|
35723
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
35724
|
+
return this;
|
|
35725
|
+
}
|
|
35726
|
+
|
|
35727
|
+
clear() {
|
|
35728
|
+
this.data = createNode([]);
|
|
35729
|
+
return this;
|
|
35730
|
+
}
|
|
35731
|
+
|
|
35732
|
+
remove(item, equalsFn) {
|
|
35733
|
+
if (!item) return this;
|
|
35734
|
+
|
|
35735
|
+
let node = this.data;
|
|
35736
|
+
const bbox = this.toBBox(item);
|
|
35737
|
+
const path = [];
|
|
35738
|
+
const indexes = [];
|
|
35739
|
+
let i, parent, goingUp;
|
|
35740
|
+
|
|
35741
|
+
// depth-first iterative tree traversal
|
|
35742
|
+
while (node || path.length) {
|
|
35743
|
+
|
|
35744
|
+
if (!node) { // go up
|
|
35745
|
+
node = path.pop();
|
|
35746
|
+
parent = path[path.length - 1];
|
|
35747
|
+
i = indexes.pop();
|
|
35748
|
+
goingUp = true;
|
|
35749
|
+
}
|
|
35750
|
+
|
|
35751
|
+
if (node.leaf) { // check current node
|
|
35752
|
+
const index = findItem(item, node.children, equalsFn);
|
|
35753
|
+
|
|
35754
|
+
if (index !== -1) {
|
|
35755
|
+
// item found, remove the item and condense tree upwards
|
|
35756
|
+
node.children.splice(index, 1);
|
|
35757
|
+
path.push(node);
|
|
35758
|
+
this._condense(path);
|
|
35759
|
+
return this;
|
|
35760
|
+
}
|
|
35761
|
+
}
|
|
35762
|
+
|
|
35763
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
35764
|
+
path.push(node);
|
|
35765
|
+
indexes.push(i);
|
|
35766
|
+
i = 0;
|
|
35767
|
+
parent = node;
|
|
35768
|
+
node = node.children[0];
|
|
35769
|
+
|
|
35770
|
+
} else if (parent) { // go right
|
|
35771
|
+
i++;
|
|
35772
|
+
node = parent.children[i];
|
|
35773
|
+
goingUp = false;
|
|
35774
|
+
|
|
35775
|
+
} else node = null; // nothing found
|
|
35776
|
+
}
|
|
35777
|
+
|
|
35778
|
+
return this;
|
|
35779
|
+
}
|
|
35780
|
+
|
|
35781
|
+
toBBox(item) { return item; }
|
|
35782
|
+
|
|
35783
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
35784
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
35785
|
+
|
|
35786
|
+
toJSON() { return this.data; }
|
|
35787
|
+
|
|
35788
|
+
fromJSON(data) {
|
|
35789
|
+
this.data = data;
|
|
35790
|
+
return this;
|
|
35791
|
+
}
|
|
35792
|
+
|
|
35793
|
+
_all(node, result) {
|
|
35794
|
+
const nodesToSearch = [];
|
|
35795
|
+
while (node) {
|
|
35796
|
+
if (node.leaf) result.push(...node.children);
|
|
35797
|
+
else nodesToSearch.push(...node.children);
|
|
35798
|
+
|
|
35799
|
+
node = nodesToSearch.pop();
|
|
35800
|
+
}
|
|
35801
|
+
return result;
|
|
35802
|
+
}
|
|
35803
|
+
|
|
35804
|
+
_build(items, left, right, height) {
|
|
35805
|
+
|
|
35806
|
+
const N = right - left + 1;
|
|
35807
|
+
let M = this._maxEntries;
|
|
35808
|
+
let node;
|
|
35809
|
+
|
|
35810
|
+
if (N <= M) {
|
|
35811
|
+
// reached leaf level; return leaf
|
|
35812
|
+
node = createNode(items.slice(left, right + 1));
|
|
35813
|
+
calcBBox(node, this.toBBox);
|
|
35814
|
+
return node;
|
|
35815
|
+
}
|
|
35816
|
+
|
|
35817
|
+
if (!height) {
|
|
35818
|
+
// target height of the bulk-loaded tree
|
|
35819
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
35820
|
+
|
|
35821
|
+
// target number of root entries to maximize storage utilization
|
|
35822
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
35823
|
+
}
|
|
35824
|
+
|
|
35825
|
+
node = createNode([]);
|
|
35826
|
+
node.leaf = false;
|
|
35827
|
+
node.height = height;
|
|
35828
|
+
|
|
35829
|
+
// split the items into M mostly square tiles
|
|
35830
|
+
|
|
35831
|
+
const N2 = Math.ceil(N / M);
|
|
35832
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
35833
|
+
|
|
35834
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
35835
|
+
|
|
35836
|
+
for (let i = left; i <= right; i += N1) {
|
|
35837
|
+
|
|
35838
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
35839
|
+
|
|
35840
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
35841
|
+
|
|
35842
|
+
for (let j = i; j <= right2; j += N2) {
|
|
35843
|
+
|
|
35844
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
35845
|
+
|
|
35846
|
+
// pack each entry recursively
|
|
35847
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
35848
|
+
}
|
|
35849
|
+
}
|
|
35850
|
+
|
|
35851
|
+
calcBBox(node, this.toBBox);
|
|
35852
|
+
|
|
35853
|
+
return node;
|
|
35854
|
+
}
|
|
35855
|
+
|
|
35856
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
35857
|
+
while (true) {
|
|
35858
|
+
path.push(node);
|
|
35859
|
+
|
|
35860
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
35861
|
+
|
|
35862
|
+
let minArea = Infinity;
|
|
35863
|
+
let minEnlargement = Infinity;
|
|
35864
|
+
let targetNode;
|
|
35865
|
+
|
|
35866
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35867
|
+
const child = node.children[i];
|
|
35868
|
+
const area = bboxArea(child);
|
|
35869
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
35870
|
+
|
|
35871
|
+
// choose entry with the least area enlargement
|
|
35872
|
+
if (enlargement < minEnlargement) {
|
|
35873
|
+
minEnlargement = enlargement;
|
|
35874
|
+
minArea = area < minArea ? area : minArea;
|
|
35875
|
+
targetNode = child;
|
|
35876
|
+
|
|
35877
|
+
} else if (enlargement === minEnlargement) {
|
|
35878
|
+
// otherwise choose one with the smallest area
|
|
35879
|
+
if (area < minArea) {
|
|
35880
|
+
minArea = area;
|
|
35881
|
+
targetNode = child;
|
|
35882
|
+
}
|
|
35883
|
+
}
|
|
35884
|
+
}
|
|
35885
|
+
|
|
35886
|
+
node = targetNode || node.children[0];
|
|
35887
|
+
}
|
|
35888
|
+
|
|
35889
|
+
return node;
|
|
35890
|
+
}
|
|
35891
|
+
|
|
35892
|
+
_insert(item, level, isNode) {
|
|
35893
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
35894
|
+
const insertPath = [];
|
|
35895
|
+
|
|
35896
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
35897
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
35898
|
+
|
|
35899
|
+
// put the item into the node
|
|
35900
|
+
node.children.push(item);
|
|
35901
|
+
extend(node, bbox);
|
|
35902
|
+
|
|
35903
|
+
// split on node overflow; propagate upwards if necessary
|
|
35904
|
+
while (level >= 0) {
|
|
35905
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
35906
|
+
this._split(insertPath, level);
|
|
35907
|
+
level--;
|
|
35908
|
+
} else break;
|
|
35909
|
+
}
|
|
35910
|
+
|
|
35911
|
+
// adjust bboxes along the insertion path
|
|
35912
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
35913
|
+
}
|
|
35914
|
+
|
|
35915
|
+
// split overflowed node into two
|
|
35916
|
+
_split(insertPath, level) {
|
|
35917
|
+
const node = insertPath[level];
|
|
35918
|
+
const M = node.children.length;
|
|
35919
|
+
const m = this._minEntries;
|
|
35920
|
+
|
|
35921
|
+
this._chooseSplitAxis(node, m, M);
|
|
35922
|
+
|
|
35923
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
35924
|
+
|
|
35925
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
35926
|
+
newNode.height = node.height;
|
|
35927
|
+
newNode.leaf = node.leaf;
|
|
35928
|
+
|
|
35929
|
+
calcBBox(node, this.toBBox);
|
|
35930
|
+
calcBBox(newNode, this.toBBox);
|
|
35931
|
+
|
|
35932
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
35933
|
+
else this._splitRoot(node, newNode);
|
|
35934
|
+
}
|
|
35935
|
+
|
|
35936
|
+
_splitRoot(node, newNode) {
|
|
35937
|
+
// split root node
|
|
35938
|
+
this.data = createNode([node, newNode]);
|
|
35939
|
+
this.data.height = node.height + 1;
|
|
35940
|
+
this.data.leaf = false;
|
|
35941
|
+
calcBBox(this.data, this.toBBox);
|
|
35942
|
+
}
|
|
35943
|
+
|
|
35944
|
+
_chooseSplitIndex(node, m, M) {
|
|
35945
|
+
let index;
|
|
35946
|
+
let minOverlap = Infinity;
|
|
35947
|
+
let minArea = Infinity;
|
|
35948
|
+
|
|
35949
|
+
for (let i = m; i <= M - m; i++) {
|
|
35950
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
35951
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
35952
|
+
|
|
35953
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
35954
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
35955
|
+
|
|
35956
|
+
// choose distribution with minimum overlap
|
|
35957
|
+
if (overlap < minOverlap) {
|
|
35958
|
+
minOverlap = overlap;
|
|
35959
|
+
index = i;
|
|
35960
|
+
|
|
35961
|
+
minArea = area < minArea ? area : minArea;
|
|
35962
|
+
|
|
35963
|
+
} else if (overlap === minOverlap) {
|
|
35964
|
+
// otherwise choose distribution with minimum area
|
|
35965
|
+
if (area < minArea) {
|
|
35966
|
+
minArea = area;
|
|
35967
|
+
index = i;
|
|
35968
|
+
}
|
|
35969
|
+
}
|
|
35970
|
+
}
|
|
35971
|
+
|
|
35972
|
+
return index || M - m;
|
|
35973
|
+
}
|
|
35974
|
+
|
|
35975
|
+
// sorts node children by the best axis for split
|
|
35976
|
+
_chooseSplitAxis(node, m, M) {
|
|
35977
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
35978
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
35979
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
35980
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
35981
|
+
|
|
35982
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
35983
|
+
// otherwise it's already sorted by minY
|
|
35984
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
35985
|
+
}
|
|
35986
|
+
|
|
35987
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
35988
|
+
_allDistMargin(node, m, M, compare) {
|
|
35989
|
+
node.children.sort(compare);
|
|
35990
|
+
|
|
35991
|
+
const toBBox = this.toBBox;
|
|
35992
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
35993
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
35994
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
35995
|
+
|
|
35996
|
+
for (let i = m; i < M - m; i++) {
|
|
35997
|
+
const child = node.children[i];
|
|
35998
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
35999
|
+
margin += bboxMargin(leftBBox);
|
|
36000
|
+
}
|
|
36001
|
+
|
|
36002
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
36003
|
+
const child = node.children[i];
|
|
36004
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
36005
|
+
margin += bboxMargin(rightBBox);
|
|
36006
|
+
}
|
|
36007
|
+
|
|
36008
|
+
return margin;
|
|
36009
|
+
}
|
|
36010
|
+
|
|
36011
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
36012
|
+
// adjust bboxes along the given tree path
|
|
36013
|
+
for (let i = level; i >= 0; i--) {
|
|
36014
|
+
extend(path[i], bbox);
|
|
36015
|
+
}
|
|
36016
|
+
}
|
|
36017
|
+
|
|
36018
|
+
_condense(path) {
|
|
36019
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
36020
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
36021
|
+
if (path[i].children.length === 0) {
|
|
36022
|
+
if (i > 0) {
|
|
36023
|
+
siblings = path[i - 1].children;
|
|
36024
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
36025
|
+
|
|
36026
|
+
} else this.clear();
|
|
36027
|
+
|
|
36028
|
+
} else calcBBox(path[i], this.toBBox);
|
|
36029
|
+
}
|
|
36030
|
+
}
|
|
36031
|
+
}
|
|
36032
|
+
|
|
36033
|
+
function findItem(item, items, equalsFn) {
|
|
36034
|
+
if (!equalsFn) return items.indexOf(item);
|
|
36035
|
+
|
|
36036
|
+
for (let i = 0; i < items.length; i++) {
|
|
36037
|
+
if (equalsFn(item, items[i])) return i;
|
|
36038
|
+
}
|
|
36039
|
+
return -1;
|
|
36040
|
+
}
|
|
36041
|
+
|
|
36042
|
+
// calculate node's bbox from bboxes of its children
|
|
36043
|
+
function calcBBox(node, toBBox) {
|
|
36044
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
36045
|
+
}
|
|
36046
|
+
|
|
36047
|
+
// min bounding rectangle of node children from k to p-1
|
|
36048
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
36049
|
+
if (!destNode) destNode = createNode(null);
|
|
36050
|
+
destNode.minX = Infinity;
|
|
36051
|
+
destNode.minY = Infinity;
|
|
36052
|
+
destNode.maxX = -Infinity;
|
|
36053
|
+
destNode.maxY = -Infinity;
|
|
36054
|
+
|
|
36055
|
+
for (let i = k; i < p; i++) {
|
|
36056
|
+
const child = node.children[i];
|
|
36057
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
36058
|
+
}
|
|
36059
|
+
|
|
36060
|
+
return destNode;
|
|
36061
|
+
}
|
|
36062
|
+
|
|
36063
|
+
function extend(a, b) {
|
|
36064
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
36065
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
36066
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
36067
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
36068
|
+
return a;
|
|
36069
|
+
}
|
|
36070
|
+
|
|
36071
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
36072
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
36073
|
+
|
|
36074
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
36075
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
36076
|
+
|
|
36077
|
+
function enlargedArea(a, b) {
|
|
36078
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
36079
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
36080
|
+
}
|
|
36081
|
+
|
|
36082
|
+
function intersectionArea(a, b) {
|
|
36083
|
+
const minX = Math.max(a.minX, b.minX);
|
|
36084
|
+
const minY = Math.max(a.minY, b.minY);
|
|
36085
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
36086
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
36087
|
+
|
|
36088
|
+
return Math.max(0, maxX - minX) *
|
|
36089
|
+
Math.max(0, maxY - minY);
|
|
36090
|
+
}
|
|
36091
|
+
|
|
36092
|
+
function contains(a, b) {
|
|
36093
|
+
return a.minX <= b.minX &&
|
|
36094
|
+
a.minY <= b.minY &&
|
|
36095
|
+
b.maxX <= a.maxX &&
|
|
36096
|
+
b.maxY <= a.maxY;
|
|
36097
|
+
}
|
|
36098
|
+
|
|
36099
|
+
function intersects(a, b) {
|
|
36100
|
+
return b.minX <= a.maxX &&
|
|
36101
|
+
b.minY <= a.maxY &&
|
|
36102
|
+
b.maxX >= a.minX &&
|
|
36103
|
+
b.maxY >= a.minY;
|
|
36104
|
+
}
|
|
36105
|
+
|
|
36106
|
+
function createNode(children) {
|
|
36107
|
+
return {
|
|
36108
|
+
children,
|
|
36109
|
+
height: 1,
|
|
36110
|
+
leaf: true,
|
|
36111
|
+
minX: Infinity,
|
|
36112
|
+
minY: Infinity,
|
|
36113
|
+
maxX: -Infinity,
|
|
36114
|
+
maxY: -Infinity
|
|
36115
|
+
};
|
|
36116
|
+
}
|
|
36117
|
+
|
|
36118
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
36119
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
36120
|
+
|
|
36121
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
36122
|
+
const stack = [left, right];
|
|
36123
|
+
|
|
36124
|
+
while (stack.length) {
|
|
36125
|
+
right = stack.pop();
|
|
36126
|
+
left = stack.pop();
|
|
36127
|
+
|
|
36128
|
+
if (right - left <= n) continue;
|
|
36129
|
+
|
|
36130
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
36131
|
+
quickselect(arr, mid, left, right, compare);
|
|
36132
|
+
|
|
36133
|
+
stack.push(left, mid, mid, right);
|
|
36134
|
+
}
|
|
36135
|
+
}
|
|
36136
|
+
|
|
36137
|
+
/**
|
|
36138
|
+
* R-Tree Data Structure
|
|
36139
|
+
*
|
|
36140
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
36141
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
36142
|
+
*
|
|
36143
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
36144
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
36145
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
36146
|
+
* for faster spatial queries.
|
|
36147
|
+
*
|
|
36148
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
36149
|
+
*
|
|
36150
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
36151
|
+
* +--------------------------+
|
|
36152
|
+
* | |
|
|
36153
|
+
* | +---+ +-------+ |
|
|
36154
|
+
* | | A | | B | |
|
|
36155
|
+
* | +---+ +-------+ |
|
|
36156
|
+
* | |
|
|
36157
|
+
* | |
|
|
36158
|
+
* | +---+ |
|
|
36159
|
+
* | | C | |
|
|
36160
|
+
* | +---+ |
|
|
36161
|
+
* | +-----------+ |
|
|
36162
|
+
* | | D | |
|
|
36163
|
+
* | +-----------+ |
|
|
36164
|
+
* | |
|
|
36165
|
+
* +--------------------------+
|
|
36166
|
+
*
|
|
36167
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
36168
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
36169
|
+
* in R2.
|
|
36170
|
+
*
|
|
36171
|
+
* R0
|
|
36172
|
+
* +--------------------------+
|
|
36173
|
+
* | R1 |
|
|
36174
|
+
* | +-----------------+ |
|
|
36175
|
+
* | | A | | B | |
|
|
36176
|
+
* | +-----------------+ |
|
|
36177
|
+
* | |
|
|
36178
|
+
* | R2 |
|
|
36179
|
+
* | +---+---+---+ |
|
|
36180
|
+
* | | | C | | |
|
|
36181
|
+
* | | +---+ | |
|
|
36182
|
+
* | +-----------+ |
|
|
36183
|
+
* | | D | |
|
|
36184
|
+
* | +-----------+ |
|
|
36185
|
+
* | |
|
|
36186
|
+
* +--------------------------+
|
|
36187
|
+
*
|
|
36188
|
+
* The tree would look like this:
|
|
36189
|
+
* R0
|
|
36190
|
+
* / \
|
|
36191
|
+
* / \
|
|
36192
|
+
* R1 R2
|
|
36193
|
+
* | |
|
|
36194
|
+
* A,B C,D
|
|
36195
|
+
|
|
36196
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
36197
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
36198
|
+
* to reduce the number of subtrees processed during searches.
|
|
36199
|
+
*
|
|
36200
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
36201
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
36202
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
36203
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
36204
|
+
*
|
|
36205
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
36206
|
+
* with a zone and a sheet.
|
|
36207
|
+
*
|
|
36208
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
36209
|
+
* @see https://github.com/mourner/rbush
|
|
36210
|
+
*/
|
|
36211
|
+
class SpreadsheetRTree {
|
|
36212
|
+
/**
|
|
36213
|
+
* One 2D R-tree per sheet
|
|
36214
|
+
*/
|
|
36215
|
+
rTrees = {};
|
|
36216
|
+
/**
|
|
36217
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
36218
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
36219
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
36220
|
+
*/
|
|
36221
|
+
constructor(items = []) {
|
|
36222
|
+
const rangesPerSheet = {};
|
|
36223
|
+
for (const item of items) {
|
|
36224
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36225
|
+
if (!rangesPerSheet[sheetId]) {
|
|
36226
|
+
rangesPerSheet[sheetId] = [];
|
|
36227
|
+
}
|
|
36228
|
+
rangesPerSheet[sheetId].push(item);
|
|
36229
|
+
}
|
|
36230
|
+
for (const sheetId in rangesPerSheet) {
|
|
36231
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
36232
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
36233
|
+
}
|
|
36234
|
+
}
|
|
36235
|
+
insert(item) {
|
|
36236
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36237
|
+
if (!this.rTrees[sheetId]) {
|
|
36238
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
36239
|
+
}
|
|
36240
|
+
this.rTrees[sheetId].insert(item);
|
|
36241
|
+
}
|
|
36242
|
+
search({ zone, sheetId }) {
|
|
36243
|
+
if (!this.rTrees[sheetId]) {
|
|
36244
|
+
return [];
|
|
36245
|
+
}
|
|
36246
|
+
return this.rTrees[sheetId].search({
|
|
36247
|
+
minX: zone.left,
|
|
36248
|
+
minY: zone.top,
|
|
36249
|
+
maxX: zone.right,
|
|
36250
|
+
maxY: zone.bottom,
|
|
36251
|
+
});
|
|
36252
|
+
}
|
|
36253
|
+
remove(item) {
|
|
36254
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36255
|
+
if (!this.rTrees[sheetId]) {
|
|
36256
|
+
return;
|
|
36257
|
+
}
|
|
36258
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
36259
|
+
}
|
|
36260
|
+
}
|
|
36261
|
+
/**
|
|
36262
|
+
* RBush extension to use zones as bounding boxes
|
|
36263
|
+
*/
|
|
36264
|
+
class ZoneRBush extends RBush {
|
|
36265
|
+
toBBox({ boundingBox }) {
|
|
36266
|
+
const zone = boundingBox.zone;
|
|
36267
|
+
return {
|
|
36268
|
+
minX: zone.left,
|
|
36269
|
+
minY: zone.top,
|
|
36270
|
+
maxX: zone.right,
|
|
36271
|
+
maxY: zone.bottom,
|
|
36272
|
+
};
|
|
36273
|
+
}
|
|
36274
|
+
compareMinX(a, b) {
|
|
36275
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
36276
|
+
}
|
|
36277
|
+
compareMinY(a, b) {
|
|
36278
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
36279
|
+
}
|
|
36280
|
+
}
|
|
36281
|
+
|
|
35559
36282
|
/**
|
|
35560
|
-
*
|
|
36283
|
+
* Implementation of a dependency Graph.
|
|
35561
36284
|
* The graph is used to evaluate the cells in the correct
|
|
35562
36285
|
* order, and should be updated each time a cell's content is modified
|
|
35563
36286
|
*
|
|
36287
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
35564
36288
|
*/
|
|
35565
36289
|
class FormulaDependencyGraph {
|
|
35566
|
-
|
|
35567
|
-
* Internal structure:
|
|
35568
|
-
* - key: a cell position (encoded as an integer)
|
|
35569
|
-
* - value: a set of cell positions that depends on the key
|
|
35570
|
-
*
|
|
35571
|
-
* Given
|
|
35572
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
35573
|
-
* - C1:"= B1";
|
|
35574
|
-
* - C2:"= C1"
|
|
35575
|
-
*
|
|
35576
|
-
* we will have something like:
|
|
35577
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
35578
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
35579
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
35580
|
-
*/
|
|
35581
|
-
inverseDependencies = new Map();
|
|
36290
|
+
encoder;
|
|
35582
36291
|
dependencies = new Map();
|
|
36292
|
+
rTree;
|
|
36293
|
+
constructor(encoder, data = []) {
|
|
36294
|
+
this.encoder = encoder;
|
|
36295
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
36296
|
+
}
|
|
35583
36297
|
removeAllDependencies(formulaPositionId) {
|
|
35584
|
-
const
|
|
35585
|
-
if (!
|
|
36298
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
36299
|
+
if (!ranges) {
|
|
35586
36300
|
return;
|
|
35587
36301
|
}
|
|
35588
|
-
for (const
|
|
35589
|
-
this.
|
|
36302
|
+
for (const range of ranges) {
|
|
36303
|
+
this.rTree.remove(range);
|
|
35590
36304
|
}
|
|
35591
36305
|
this.dependencies.delete(formulaPositionId);
|
|
35592
36306
|
}
|
|
35593
36307
|
addDependencies(formulaPositionId, dependencies) {
|
|
35594
|
-
|
|
35595
|
-
|
|
35596
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
|
|
35600
|
-
|
|
35601
|
-
|
|
36308
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
36309
|
+
data: formulaPositionId,
|
|
36310
|
+
boundingBox: {
|
|
36311
|
+
zone,
|
|
36312
|
+
sheetId,
|
|
36313
|
+
},
|
|
36314
|
+
}));
|
|
36315
|
+
for (const item of rTreeItems) {
|
|
36316
|
+
this.rTree.insert(item);
|
|
35602
36317
|
}
|
|
35603
36318
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
35604
36319
|
if (existingDependencies) {
|
|
35605
|
-
existingDependencies.push(...
|
|
36320
|
+
existingDependencies.push(...rTreeItems);
|
|
35606
36321
|
}
|
|
35607
36322
|
else {
|
|
35608
|
-
this.dependencies.set(formulaPositionId,
|
|
36323
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
35609
36324
|
}
|
|
35610
36325
|
}
|
|
35611
36326
|
/**
|
|
@@ -35613,20 +36328,20 @@
|
|
|
35613
36328
|
* in the correct order they should be evaluated.
|
|
35614
36329
|
* This is called a topological ordering (excluding cycles)
|
|
35615
36330
|
*/
|
|
35616
|
-
getCellsDependingOn(
|
|
36331
|
+
getCellsDependingOn(ranges) {
|
|
35617
36332
|
const visited = new JetSet();
|
|
35618
|
-
const queue = Array.from(
|
|
36333
|
+
const queue = Array.from(ranges).reverse();
|
|
35619
36334
|
while (queue.length > 0) {
|
|
35620
|
-
const
|
|
35621
|
-
visited.add(
|
|
35622
|
-
const
|
|
35623
|
-
for (const
|
|
35624
|
-
if (!visited.has(
|
|
35625
|
-
queue.push(
|
|
36335
|
+
const range = queue.pop();
|
|
36336
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
36337
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
36338
|
+
for (const positionId of impactedPositionIds) {
|
|
36339
|
+
if (!visited.has(positionId)) {
|
|
36340
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
35626
36341
|
}
|
|
35627
36342
|
}
|
|
35628
36343
|
}
|
|
35629
|
-
visited.delete(...
|
|
36344
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
35630
36345
|
return visited;
|
|
35631
36346
|
}
|
|
35632
36347
|
}
|
|
@@ -35714,9 +36429,9 @@
|
|
|
35714
36429
|
context;
|
|
35715
36430
|
getters;
|
|
35716
36431
|
compilationParams;
|
|
35717
|
-
|
|
36432
|
+
encoder = new PositionBitsEncoder();
|
|
35718
36433
|
evaluatedCells = new Map();
|
|
35719
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
36434
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
35720
36435
|
blockedArrayFormulas = new Set();
|
|
35721
36436
|
spreadingRelations = new SpreadingRelation();
|
|
35722
36437
|
constructor(context, getters) {
|
|
@@ -35725,16 +36440,16 @@
|
|
|
35725
36440
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
35726
36441
|
}
|
|
35727
36442
|
getEvaluatedCell(position) {
|
|
35728
|
-
return (this.evaluatedCells.get(this.
|
|
36443
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
35729
36444
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
35730
36445
|
}
|
|
35731
36446
|
getArrayFormulaSpreadingOn(position) {
|
|
35732
|
-
const positionId = this.
|
|
36447
|
+
const positionId = this.encoder.encode(position);
|
|
35733
36448
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
35734
|
-
return formulaPosition !== undefined ? this.
|
|
36449
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
35735
36450
|
}
|
|
35736
36451
|
getEvaluatedPositions() {
|
|
35737
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
36452
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
35738
36453
|
}
|
|
35739
36454
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
35740
36455
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -35744,7 +36459,7 @@
|
|
|
35744
36459
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
35745
36460
|
}
|
|
35746
36461
|
updateDependencies(position) {
|
|
35747
|
-
const positionId = this.
|
|
36462
|
+
const positionId = this.encoder.encode(position);
|
|
35748
36463
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
35749
36464
|
const dependencies = this.getDirectDependencies(positionId);
|
|
35750
36465
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -35754,12 +36469,12 @@
|
|
|
35754
36469
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
35755
36470
|
}
|
|
35756
36471
|
evaluateCells(positions) {
|
|
35757
|
-
const cells = positions.map(this.
|
|
36472
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
35758
36473
|
const cellsToCompute = new JetSet(cells);
|
|
35759
|
-
const
|
|
36474
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
35760
36475
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
35761
|
-
cellsToCompute.add(...
|
|
35762
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
36476
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
36477
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
35763
36478
|
this.evaluate(cellsToCompute);
|
|
35764
36479
|
}
|
|
35765
36480
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -35782,12 +36497,14 @@
|
|
|
35782
36497
|
this.blockedArrayFormulas = new Set();
|
|
35783
36498
|
this.spreadingRelations = new SpreadingRelation();
|
|
35784
36499
|
this.formulaDependencies = lazy(() => {
|
|
35785
|
-
const
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
36500
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
36501
|
+
data: positionId,
|
|
36502
|
+
boundingBox: {
|
|
36503
|
+
zone: range.zone,
|
|
36504
|
+
sheetId: range.sheetId,
|
|
36505
|
+
},
|
|
36506
|
+
})));
|
|
36507
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
35791
36508
|
});
|
|
35792
36509
|
}
|
|
35793
36510
|
evaluateAllCells() {
|
|
@@ -35808,7 +36525,7 @@
|
|
|
35808
36525
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
35809
36526
|
const cellIds = this.getters.getCells(sheetId);
|
|
35810
36527
|
for (const cellId in cellIds) {
|
|
35811
|
-
positionIds.add(this.
|
|
36528
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
35812
36529
|
}
|
|
35813
36530
|
}
|
|
35814
36531
|
return positionIds;
|
|
@@ -35875,7 +36592,7 @@
|
|
|
35875
36592
|
}
|
|
35876
36593
|
}
|
|
35877
36594
|
computeAndSave(position) {
|
|
35878
|
-
const positionId = this.
|
|
36595
|
+
const positionId = this.encoder.encode(position);
|
|
35879
36596
|
const evaluatedCell = this.computeCell(positionId);
|
|
35880
36597
|
if (!this.evaluatedCells.has(positionId)) {
|
|
35881
36598
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -35936,15 +36653,15 @@
|
|
|
35936
36653
|
throw new Error(_lt("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
35937
36654
|
}
|
|
35938
36655
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
35939
|
-
const arrayFormulaPositionId = this.
|
|
36656
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
35940
36657
|
return (i, j) => {
|
|
35941
36658
|
const position = { sheetId, col: i + col, row: j + row };
|
|
35942
|
-
const resultPositionId = this.
|
|
36659
|
+
const resultPositionId = this.encoder.encode(position);
|
|
35943
36660
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
35944
36661
|
};
|
|
35945
36662
|
}
|
|
35946
36663
|
checkCollision({ sheetId, col, row }) {
|
|
35947
|
-
const formulaPositionId = this.
|
|
36664
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
35948
36665
|
return (i, j) => {
|
|
35949
36666
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
35950
36667
|
const rawCell = this.getters.getCell(position);
|
|
@@ -35966,7 +36683,7 @@
|
|
|
35966
36683
|
format: format || formatFromPosition(i, j),
|
|
35967
36684
|
locale: this.getters.getLocale(),
|
|
35968
36685
|
});
|
|
35969
|
-
const positionId = this.
|
|
36686
|
+
const positionId = this.encoder.encode(position);
|
|
35970
36687
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
35971
36688
|
// check if formula dependencies present in the spread zone
|
|
35972
36689
|
// if so, they need to be recomputed
|
|
@@ -35998,29 +36715,17 @@
|
|
|
35998
36715
|
if (!cell?.isFormula) {
|
|
35999
36716
|
return [];
|
|
36000
36717
|
}
|
|
36001
|
-
|
|
36002
|
-
for (const range of cell.dependencies) {
|
|
36003
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
36004
|
-
continue;
|
|
36005
|
-
}
|
|
36006
|
-
const sheetId = range.sheetId;
|
|
36007
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
36008
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
36009
|
-
});
|
|
36010
|
-
}
|
|
36011
|
-
return dependencies;
|
|
36718
|
+
return cell.dependencies;
|
|
36012
36719
|
}
|
|
36013
36720
|
getCellsDependingOn(positionIds) {
|
|
36014
|
-
|
|
36721
|
+
const ranges = [];
|
|
36722
|
+
for (const positionId of positionIds) {
|
|
36723
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
36724
|
+
}
|
|
36725
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
36015
36726
|
}
|
|
36016
36727
|
getCell(positionId) {
|
|
36017
|
-
return this.getters.getCell(this.
|
|
36018
|
-
}
|
|
36019
|
-
encodePosition(position) {
|
|
36020
|
-
return this.positionEncoder.encode(position);
|
|
36021
|
-
}
|
|
36022
|
-
decodePosition(positionId) {
|
|
36023
|
-
return this.positionEncoder.decode(positionId);
|
|
36728
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
36024
36729
|
}
|
|
36025
36730
|
}
|
|
36026
36731
|
function forEachSpreadPositionInMatrix(matrix, callback) {
|
|
@@ -36095,6 +36800,13 @@
|
|
|
36095
36800
|
encode({ sheetId, col, row }) {
|
|
36096
36801
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
36097
36802
|
}
|
|
36803
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
36804
|
+
const positions = [];
|
|
36805
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
36806
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
36807
|
+
});
|
|
36808
|
+
return positions;
|
|
36809
|
+
}
|
|
36098
36810
|
decode(id) {
|
|
36099
36811
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
36100
36812
|
const row = Number(id & 2097151n);
|
|
@@ -36102,6 +36814,10 @@
|
|
|
36102
36814
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
36103
36815
|
return { sheetId, col, row };
|
|
36104
36816
|
}
|
|
36817
|
+
decodeToBoundingBox(id) {
|
|
36818
|
+
const { sheetId, col, row } = this.decode(id);
|
|
36819
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
36820
|
+
}
|
|
36105
36821
|
encodeSheet(sheetId) {
|
|
36106
36822
|
const sheetKey = this.sheetMapping[sheetId];
|
|
36107
36823
|
if (sheetKey === undefined) {
|
|
@@ -36216,6 +36932,7 @@
|
|
|
36216
36932
|
class EvaluationPlugin extends UIPlugin {
|
|
36217
36933
|
static getters = [
|
|
36218
36934
|
"evaluateFormula",
|
|
36935
|
+
"getCorrespondingFormulaCell",
|
|
36219
36936
|
"getRangeFormattedValues",
|
|
36220
36937
|
"getRangeValues",
|
|
36221
36938
|
"getRangeFormats",
|
|
@@ -36269,7 +36986,12 @@
|
|
|
36269
36986
|
// Getters
|
|
36270
36987
|
// ---------------------------------------------------------------------------
|
|
36271
36988
|
evaluateFormula(sheetId, formulaString) {
|
|
36272
|
-
|
|
36989
|
+
try {
|
|
36990
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
36991
|
+
}
|
|
36992
|
+
catch (error) {
|
|
36993
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
36994
|
+
}
|
|
36273
36995
|
}
|
|
36274
36996
|
/**
|
|
36275
36997
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -36532,7 +37254,7 @@
|
|
|
36532
37254
|
}
|
|
36533
37255
|
|
|
36534
37256
|
class EvaluationChartPlugin extends UIPlugin {
|
|
36535
|
-
static getters = ["getChartRuntime", "
|
|
37257
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
36536
37258
|
charts = {};
|
|
36537
37259
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
36538
37260
|
handle(cmd) {
|
|
@@ -36570,25 +37292,26 @@
|
|
|
36570
37292
|
return this.charts[figureId];
|
|
36571
37293
|
}
|
|
36572
37294
|
/**
|
|
36573
|
-
* Get the background
|
|
36574
|
-
* of the chart. In order of priority, it will return :
|
|
36575
|
-
*
|
|
36576
|
-
* - the chart background color if one is defined
|
|
36577
|
-
* - the fill color of the cell if one is defined
|
|
36578
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
36579
|
-
* - the default chart color if no other color is defined
|
|
37295
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
36580
37296
|
*/
|
|
36581
|
-
|
|
37297
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
36582
37298
|
if (chartBackground)
|
|
36583
|
-
return chartBackground;
|
|
37299
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
36584
37300
|
if (!mainRange) {
|
|
36585
|
-
return
|
|
37301
|
+
return {
|
|
37302
|
+
background: BACKGROUND_CHART_COLOR,
|
|
37303
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
37304
|
+
};
|
|
36586
37305
|
}
|
|
36587
37306
|
const col = mainRange.zone.left;
|
|
36588
37307
|
const row = mainRange.zone.top;
|
|
36589
37308
|
const sheetId = mainRange.sheetId;
|
|
36590
37309
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
36591
|
-
|
|
37310
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
37311
|
+
return {
|
|
37312
|
+
background,
|
|
37313
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
37314
|
+
};
|
|
36592
37315
|
}
|
|
36593
37316
|
}
|
|
36594
37317
|
|
|
@@ -36658,34 +37381,29 @@
|
|
|
36658
37381
|
getComputedStyles(sheetId) {
|
|
36659
37382
|
const computedStyle = {};
|
|
36660
37383
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
36661
|
-
|
|
36662
|
-
|
|
36663
|
-
|
|
36664
|
-
|
|
36665
|
-
|
|
36666
|
-
|
|
36667
|
-
|
|
36668
|
-
|
|
36669
|
-
|
|
36670
|
-
|
|
36671
|
-
for (let
|
|
36672
|
-
|
|
36673
|
-
|
|
36674
|
-
|
|
36675
|
-
if (
|
|
36676
|
-
|
|
36677
|
-
|
|
36678
|
-
|
|
36679
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
36680
|
-
}
|
|
37384
|
+
switch (cf.rule.type) {
|
|
37385
|
+
case "ColorScaleRule":
|
|
37386
|
+
for (let range of cf.ranges) {
|
|
37387
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
37388
|
+
}
|
|
37389
|
+
break;
|
|
37390
|
+
case "CellIsRule":
|
|
37391
|
+
for (let ref of cf.ranges) {
|
|
37392
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
37393
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
37394
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
37395
|
+
const pr = this.rulePredicate[cf.rule.type];
|
|
37396
|
+
let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
|
|
37397
|
+
if (pr && pr(cell, cf.rule)) {
|
|
37398
|
+
if (!computedStyle[col])
|
|
37399
|
+
computedStyle[col] = [];
|
|
37400
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
37401
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
36681
37402
|
}
|
|
36682
37403
|
}
|
|
36683
37404
|
}
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
}
|
|
36687
|
-
catch (_) {
|
|
36688
|
-
// we don't care about the errors within the evaluation of a rule
|
|
37405
|
+
}
|
|
37406
|
+
break;
|
|
36689
37407
|
}
|
|
36690
37408
|
}
|
|
36691
37409
|
return computedStyle;
|
|
@@ -36721,7 +37439,7 @@
|
|
|
36721
37439
|
return percentile(rangeValues, Number(threshold.value) / 100, true);
|
|
36722
37440
|
case "formula":
|
|
36723
37441
|
const value = threshold.value && this.getters.evaluateFormula(sheetId, threshold.value);
|
|
36724
|
-
return
|
|
37442
|
+
return typeof value === "number" ? value : null;
|
|
36725
37443
|
default:
|
|
36726
37444
|
return null;
|
|
36727
37445
|
}
|
|
@@ -37287,19 +38005,21 @@
|
|
|
37287
38005
|
let col = zone.left;
|
|
37288
38006
|
let row = zone.bottom;
|
|
37289
38007
|
if (col > 0) {
|
|
37290
|
-
let
|
|
37291
|
-
while (
|
|
38008
|
+
let leftPosition = { sheetId, col: col - 1, row };
|
|
38009
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
38010
|
+
this.getters.getCell(leftPosition)?.content) {
|
|
37292
38011
|
row += 1;
|
|
37293
|
-
|
|
38012
|
+
leftPosition = { sheetId, col: col - 1, row };
|
|
37294
38013
|
}
|
|
37295
38014
|
}
|
|
37296
38015
|
if (row === zone.bottom) {
|
|
37297
38016
|
col = zone.right;
|
|
37298
38017
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
37299
|
-
let
|
|
37300
|
-
while (
|
|
38018
|
+
let rightPosition = { sheetId, col: col + 1, row };
|
|
38019
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
38020
|
+
this.getters.getCell(rightPosition)?.content) {
|
|
37301
38021
|
row += 1;
|
|
37302
|
-
|
|
38022
|
+
rightPosition = { sheetId, col: col + 1, row };
|
|
37303
38023
|
}
|
|
37304
38024
|
}
|
|
37305
38025
|
}
|
|
@@ -40217,6 +40937,9 @@
|
|
|
40217
40937
|
// Other
|
|
40218
40938
|
// ---------------------------------------------------------------------------
|
|
40219
40939
|
initInput(id, initialRanges, inputHasSingleRange = false) {
|
|
40940
|
+
if (this.inputs[id]) {
|
|
40941
|
+
this.unfocus();
|
|
40942
|
+
}
|
|
40220
40943
|
this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
|
|
40221
40944
|
if (initialRanges.length === 0) {
|
|
40222
40945
|
const input = this.inputs[id];
|
|
@@ -42332,6 +43055,10 @@
|
|
|
42332
43055
|
return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
42333
43056
|
case "colrow":
|
|
42334
43057
|
indexOfNumber = xc.search(/[0-9]/);
|
|
43058
|
+
if (indexOfNumber === -1 || indexOfNumber === 0) {
|
|
43059
|
+
// no row number (eg. A) or no column (eg. 1)
|
|
43060
|
+
return "$" + xc;
|
|
43061
|
+
}
|
|
42335
43062
|
xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
42336
43063
|
return "$" + xc;
|
|
42337
43064
|
case "none":
|
|
@@ -43747,7 +44474,7 @@
|
|
|
43747
44474
|
sheetId: cmd.sheetId,
|
|
43748
44475
|
base: cmd.base,
|
|
43749
44476
|
quantity: thickness,
|
|
43750
|
-
position:
|
|
44477
|
+
position: cmd.position,
|
|
43751
44478
|
});
|
|
43752
44479
|
const isCol = cmd.dimension === "COL";
|
|
43753
44480
|
const start = cmd.elements[0];
|
|
@@ -43764,12 +44491,13 @@
|
|
|
43764
44491
|
},
|
|
43765
44492
|
];
|
|
43766
44493
|
const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
|
|
44494
|
+
const base = isBasedBefore ? cmd.base : cmd.base + 1;
|
|
43767
44495
|
const pasteTarget = [
|
|
43768
44496
|
{
|
|
43769
|
-
left: isCol ?
|
|
43770
|
-
right: isCol ?
|
|
43771
|
-
top: !isCol ?
|
|
43772
|
-
bottom: !isCol ?
|
|
44497
|
+
left: isCol ? base : 0,
|
|
44498
|
+
right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
|
|
44499
|
+
top: !isCol ? base : 0,
|
|
44500
|
+
bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
|
|
43773
44501
|
},
|
|
43774
44502
|
];
|
|
43775
44503
|
state.paste(pasteTarget, { selectTarget: true });
|
|
@@ -43806,6 +44534,11 @@
|
|
|
43806
44534
|
doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
|
|
43807
44535
|
return 2 /* CommandResult.WillRemoveExistingMerge */;
|
|
43808
44536
|
}
|
|
44537
|
+
const headers = [cmd.base, ...cmd.elements];
|
|
44538
|
+
const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
|
|
44539
|
+
if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
|
|
44540
|
+
return 88 /* CommandResult.InvalidHeaderIndex */;
|
|
44541
|
+
}
|
|
43809
44542
|
return 0 /* CommandResult.Success */;
|
|
43810
44543
|
}
|
|
43811
44544
|
//-------------------------------------------
|
|
@@ -47031,7 +47764,7 @@
|
|
|
47031
47764
|
}
|
|
47032
47765
|
onKeydown(ev) {
|
|
47033
47766
|
let keyDownString = "";
|
|
47034
|
-
if (ev
|
|
47767
|
+
if (isCtrlKey(ev)) {
|
|
47035
47768
|
keyDownString += "CTRL+";
|
|
47036
47769
|
}
|
|
47037
47770
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -50792,9 +51525,9 @@
|
|
|
50792
51525
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
50793
51526
|
|
|
50794
51527
|
|
|
50795
|
-
__info__.version = '16.4.
|
|
50796
|
-
__info__.date = '
|
|
50797
|
-
__info__.hash = '
|
|
51528
|
+
__info__.version = '16.4.17';
|
|
51529
|
+
__info__.date = '2024-01-04T12:22:53.125Z';
|
|
51530
|
+
__info__.hash = '769f8a3';
|
|
50798
51531
|
|
|
50799
51532
|
|
|
50800
51533
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|