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