@odoo/o-spreadsheet 17.0.5 → 17.0.7
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 +998 -218
- package/dist/o-spreadsheet.d.ts +47 -18
- package/dist/o-spreadsheet.esm.js +998 -218
- package/dist/o-spreadsheet.iife.js +998 -218
- package/dist/o-spreadsheet.iife.min.js +245 -245
- package/dist/o_spreadsheet.xml +3 -3
- package/package.json +6 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 17.0.
|
|
6
|
-
* @date
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.0.7
|
|
6
|
+
* @date 2024-01-12T13:42:04.654Z
|
|
7
|
+
* @hash cadb817
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
|
|
@@ -329,13 +329,6 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
|
|
|
329
329
|
//------------------------------------------------------------------------------
|
|
330
330
|
// Miscellaneous
|
|
331
331
|
//------------------------------------------------------------------------------
|
|
332
|
-
/**
|
|
333
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
334
|
-
* is ordered.
|
|
335
|
-
*/
|
|
336
|
-
function stringify(obj) {
|
|
337
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
338
|
-
}
|
|
339
332
|
/**
|
|
340
333
|
* Remove quotes from a quoted string
|
|
341
334
|
* ```js
|
|
@@ -558,7 +551,7 @@ function isObjectEmptyRecursive(argument) {
|
|
|
558
551
|
*/
|
|
559
552
|
function getItemId(item, itemsDic) {
|
|
560
553
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
561
|
-
if (
|
|
554
|
+
if (deepEquals(value, item)) {
|
|
562
555
|
return parseInt(key, 10);
|
|
563
556
|
}
|
|
564
557
|
}
|
|
@@ -1208,16 +1201,84 @@ function toXC(col, row, rangePart = { colFixed: false, rowFixed: false }) {
|
|
|
1208
1201
|
// -----------------------------------------------------------------------------
|
|
1209
1202
|
// Date Type
|
|
1210
1203
|
// -----------------------------------------------------------------------------
|
|
1204
|
+
/**
|
|
1205
|
+
* A DateTime object that can be used to manipulate spreadsheet dates.
|
|
1206
|
+
* Conceptually, a spreadsheet date is simply a number with a date format,
|
|
1207
|
+
* and it is timezone-agnostic.
|
|
1208
|
+
* This DateTime object consistently uses UTC time to represent a naive date and time.
|
|
1209
|
+
*/
|
|
1210
|
+
class DateTime {
|
|
1211
|
+
jsDate;
|
|
1212
|
+
constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
|
|
1213
|
+
this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
|
|
1214
|
+
}
|
|
1215
|
+
static fromTimestamp(timestamp) {
|
|
1216
|
+
const date = new Date(timestamp);
|
|
1217
|
+
return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
|
|
1218
|
+
}
|
|
1219
|
+
static now() {
|
|
1220
|
+
const now = new Date();
|
|
1221
|
+
return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
|
|
1222
|
+
}
|
|
1223
|
+
toString() {
|
|
1224
|
+
return this.jsDate.toString();
|
|
1225
|
+
}
|
|
1226
|
+
toLocaleDateString() {
|
|
1227
|
+
return this.jsDate.toLocaleDateString();
|
|
1228
|
+
}
|
|
1229
|
+
getTime() {
|
|
1230
|
+
return this.jsDate.getTime();
|
|
1231
|
+
}
|
|
1232
|
+
getFullYear() {
|
|
1233
|
+
return this.jsDate.getUTCFullYear();
|
|
1234
|
+
}
|
|
1235
|
+
getMonth() {
|
|
1236
|
+
return this.jsDate.getUTCMonth();
|
|
1237
|
+
}
|
|
1238
|
+
getDate() {
|
|
1239
|
+
return this.jsDate.getUTCDate();
|
|
1240
|
+
}
|
|
1241
|
+
getDay() {
|
|
1242
|
+
return this.jsDate.getUTCDay();
|
|
1243
|
+
}
|
|
1244
|
+
getHours() {
|
|
1245
|
+
return this.jsDate.getUTCHours();
|
|
1246
|
+
}
|
|
1247
|
+
getMinutes() {
|
|
1248
|
+
return this.jsDate.getUTCMinutes();
|
|
1249
|
+
}
|
|
1250
|
+
getSeconds() {
|
|
1251
|
+
return this.jsDate.getUTCSeconds();
|
|
1252
|
+
}
|
|
1253
|
+
setFullYear(year) {
|
|
1254
|
+
return this.jsDate.setUTCFullYear(year);
|
|
1255
|
+
}
|
|
1256
|
+
setMonth(month) {
|
|
1257
|
+
return this.jsDate.setUTCMonth(month);
|
|
1258
|
+
}
|
|
1259
|
+
setDate(date) {
|
|
1260
|
+
return this.jsDate.setUTCDate(date);
|
|
1261
|
+
}
|
|
1262
|
+
setHours(hours) {
|
|
1263
|
+
return this.jsDate.setUTCHours(hours);
|
|
1264
|
+
}
|
|
1265
|
+
setMinutes(minutes) {
|
|
1266
|
+
return this.jsDate.setUTCMinutes(minutes);
|
|
1267
|
+
}
|
|
1268
|
+
setSeconds(seconds) {
|
|
1269
|
+
return this.jsDate.setUTCSeconds(seconds);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1211
1272
|
// -----------------------------------------------------------------------------
|
|
1212
1273
|
// Parsing
|
|
1213
1274
|
// -----------------------------------------------------------------------------
|
|
1214
|
-
const INITIAL_1900_DAY = new
|
|
1275
|
+
const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
|
|
1215
1276
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
1216
1277
|
const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
|
|
1217
|
-
const CURRENT_YEAR =
|
|
1218
|
-
const CURRENT_MONTH =
|
|
1219
|
-
const INITIAL_JS_DAY =
|
|
1220
|
-
const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
|
|
1278
|
+
const CURRENT_YEAR = DateTime.now().getFullYear();
|
|
1279
|
+
const CURRENT_MONTH = DateTime.now().getMonth();
|
|
1280
|
+
const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
|
|
1281
|
+
const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
|
|
1221
1282
|
const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
|
|
1222
1283
|
const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
|
|
1223
1284
|
const dateSeparatorsRegex = /\/|-|\s/;
|
|
@@ -1278,7 +1339,7 @@ function _parseDateTime(str, locale) {
|
|
|
1278
1339
|
return {
|
|
1279
1340
|
value: date.value + time.value,
|
|
1280
1341
|
format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
|
|
1281
|
-
jsDate: new
|
|
1342
|
+
jsDate: new DateTime(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
|
|
1282
1343
|
};
|
|
1283
1344
|
}
|
|
1284
1345
|
return date || time;
|
|
@@ -1354,12 +1415,12 @@ function parseDate(parts, separator) {
|
|
|
1354
1415
|
// month + 1: months are 0-indexed in JS
|
|
1355
1416
|
const leadingZero = (monthStr?.length === 2 && month + 1 < 10) || (dayStr?.length === 2 && day < 10);
|
|
1356
1417
|
const fullYear = yearStr?.length !== 2;
|
|
1357
|
-
const jsDate = new
|
|
1418
|
+
const jsDate = new DateTime(year, month, day);
|
|
1358
1419
|
if (jsDate.getMonth() !== month || jsDate.getDate() !== day) {
|
|
1359
1420
|
// invalid date
|
|
1360
1421
|
return null;
|
|
1361
1422
|
}
|
|
1362
|
-
const delta = jsDate - INITIAL_1900_DAY;
|
|
1423
|
+
const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
|
|
1363
1424
|
const format = getFormatFromDateParts(parts, separator, leadingZero, fullYear);
|
|
1364
1425
|
return {
|
|
1365
1426
|
value: Math.round(delta / MS_PER_DAY),
|
|
@@ -1450,7 +1511,7 @@ function parseTime(str) {
|
|
|
1450
1511
|
if (hours >= 24) {
|
|
1451
1512
|
format = "hhhh:mm:ss";
|
|
1452
1513
|
}
|
|
1453
|
-
const jsDate = new
|
|
1514
|
+
const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
|
|
1454
1515
|
return {
|
|
1455
1516
|
value: hours / 24 + minutes / 1440 + seconds / 86400,
|
|
1456
1517
|
format: format,
|
|
@@ -1464,7 +1525,7 @@ function parseTime(str) {
|
|
|
1464
1525
|
// -----------------------------------------------------------------------------
|
|
1465
1526
|
function numberToJsDate(value) {
|
|
1466
1527
|
const truncValue = Math.trunc(value);
|
|
1467
|
-
let date =
|
|
1528
|
+
let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
|
|
1468
1529
|
let time = value - truncValue;
|
|
1469
1530
|
time = time < 0 ? 1 + time : time;
|
|
1470
1531
|
const hours = Math.round(time * 24);
|
|
@@ -1484,7 +1545,7 @@ function jsDateToNumber(date) {
|
|
|
1484
1545
|
}
|
|
1485
1546
|
/** Return the number of days in the current month of the given date */
|
|
1486
1547
|
function getDaysInMonth(date) {
|
|
1487
|
-
return new
|
|
1548
|
+
return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
|
1488
1549
|
}
|
|
1489
1550
|
function isLastDayOfMonth(date) {
|
|
1490
1551
|
return getDaysInMonth(date) === date.getDate();
|
|
@@ -1502,7 +1563,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
|
|
|
1502
1563
|
const yStart = date.getFullYear();
|
|
1503
1564
|
const mStart = date.getMonth();
|
|
1504
1565
|
const dStart = date.getDate();
|
|
1505
|
-
const jsDate = new
|
|
1566
|
+
const jsDate = new DateTime(yStart, mStart + months, 1);
|
|
1506
1567
|
if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
|
|
1507
1568
|
jsDate.setDate(getDaysInMonth(jsDate));
|
|
1508
1569
|
}
|
|
@@ -2725,20 +2786,20 @@ function flattenRowFirst(items, callback) {
|
|
|
2725
2786
|
}
|
|
2726
2787
|
|
|
2727
2788
|
function toCriterionDateNumber(dateValue) {
|
|
2728
|
-
const today =
|
|
2789
|
+
const today = DateTime.now();
|
|
2729
2790
|
switch (dateValue) {
|
|
2730
2791
|
case "today":
|
|
2731
2792
|
return jsDateToNumber(today);
|
|
2732
2793
|
case "yesterday":
|
|
2733
|
-
return jsDateToNumber(
|
|
2794
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
|
|
2734
2795
|
case "tomorrow":
|
|
2735
|
-
return jsDateToNumber(
|
|
2796
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
|
|
2736
2797
|
case "lastWeek":
|
|
2737
|
-
return jsDateToNumber(
|
|
2798
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
|
|
2738
2799
|
case "lastMonth":
|
|
2739
|
-
return jsDateToNumber(
|
|
2800
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
|
|
2740
2801
|
case "lastYear":
|
|
2741
|
-
return jsDateToNumber(
|
|
2802
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
|
|
2742
2803
|
}
|
|
2743
2804
|
}
|
|
2744
2805
|
/** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
|
|
@@ -3096,7 +3157,7 @@ function formatJSTime(jsDate, format) {
|
|
|
3096
3157
|
.map((p) => {
|
|
3097
3158
|
switch (p) {
|
|
3098
3159
|
case "hhhh":
|
|
3099
|
-
const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
|
|
3160
|
+
const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
|
|
3100
3161
|
return helapsedHours.toString();
|
|
3101
3162
|
case "hh":
|
|
3102
3163
|
return hours.toString().padStart(2, "0");
|
|
@@ -5286,7 +5347,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5286
5347
|
};
|
|
5287
5348
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5288
5349
|
}
|
|
5289
|
-
const background = getters.
|
|
5350
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5290
5351
|
const locale = getters.getLocale();
|
|
5291
5352
|
return {
|
|
5292
5353
|
title: _t(chart.title),
|
|
@@ -5295,7 +5356,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5295
5356
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5296
5357
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5297
5358
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5298
|
-
fontColor
|
|
5359
|
+
fontColor,
|
|
5299
5360
|
background,
|
|
5300
5361
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5301
5362
|
? getters.getCellStyle({
|
|
@@ -6590,6 +6651,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
6590
6651
|
},
|
|
6591
6652
|
};
|
|
6592
6653
|
|
|
6654
|
+
const macRegex = /Mac/i;
|
|
6593
6655
|
/**
|
|
6594
6656
|
* Return true if the event was triggered from
|
|
6595
6657
|
* a child element.
|
|
@@ -6629,7 +6691,15 @@ function getOpenedMenus() {
|
|
|
6629
6691
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6630
6692
|
}
|
|
6631
6693
|
function isMacOS() {
|
|
6632
|
-
return navigator.userAgent
|
|
6694
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
6695
|
+
}
|
|
6696
|
+
/**
|
|
6697
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
6698
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
6699
|
+
* On Mac, this is the "meta" or "command" key.
|
|
6700
|
+
*/
|
|
6701
|
+
function isCtrlKey(ev) {
|
|
6702
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6633
6703
|
}
|
|
6634
6704
|
|
|
6635
6705
|
/**
|
|
@@ -8878,7 +8948,7 @@ function createGaugeChartRuntime(chart, getters) {
|
|
|
8878
8948
|
});
|
|
8879
8949
|
return {
|
|
8880
8950
|
chartJsConfig: config,
|
|
8881
|
-
background: getters.
|
|
8951
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8882
8952
|
};
|
|
8883
8953
|
}
|
|
8884
8954
|
|
|
@@ -8959,7 +9029,7 @@ function getFormatMinDisplayUnit(format) {
|
|
|
8959
9029
|
else if (format.includes("h") || format.includes("H")) {
|
|
8960
9030
|
return "hour";
|
|
8961
9031
|
}
|
|
8962
|
-
else if (format.includes("
|
|
9032
|
+
else if (format.includes("d")) {
|
|
8963
9033
|
return "day";
|
|
8964
9034
|
}
|
|
8965
9035
|
else if (format.includes("M")) {
|
|
@@ -10907,6 +10977,9 @@ function arg(definition, description = "") {
|
|
|
10907
10977
|
function makeArg(str, description) {
|
|
10908
10978
|
let parts = str.match(ARG_REGEXP);
|
|
10909
10979
|
let name = parts[1].trim();
|
|
10980
|
+
if (!name) {
|
|
10981
|
+
throw new Error(`Function argument definition is missing a name: '${str}'.`);
|
|
10982
|
+
}
|
|
10910
10983
|
let types = [];
|
|
10911
10984
|
let isOptional = false;
|
|
10912
10985
|
let isRepeating = false;
|
|
@@ -14612,7 +14685,7 @@ const DATE = {
|
|
|
14612
14685
|
if (_year < 1900) {
|
|
14613
14686
|
_year += 1900;
|
|
14614
14687
|
}
|
|
14615
|
-
const jsDate = new
|
|
14688
|
+
const jsDate = new DateTime(_year, _month - 1, _day);
|
|
14616
14689
|
const result = jsDateToRoundNumber(jsDate);
|
|
14617
14690
|
assert(() => result >= 0, _t("The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900."));
|
|
14618
14691
|
return result;
|
|
@@ -14655,7 +14728,7 @@ const DATEDIF = {
|
|
|
14655
14728
|
// See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
|
|
14656
14729
|
let days = jsEndDate.getDate() - jsStartDate.getDate();
|
|
14657
14730
|
if (days < 0) {
|
|
14658
|
-
const monthBeforeEndMonth = new
|
|
14731
|
+
const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
|
|
14659
14732
|
const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
|
|
14660
14733
|
days = daysInMonthBeforeEndMonth - Math.abs(days);
|
|
14661
14734
|
}
|
|
@@ -14664,7 +14737,7 @@ const DATEDIF = {
|
|
|
14664
14737
|
if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
|
|
14665
14738
|
return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
|
|
14666
14739
|
}
|
|
14667
|
-
const endDateWithinOneYear = new
|
|
14740
|
+
const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
|
|
14668
14741
|
let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
|
|
14669
14742
|
if (days < 0) {
|
|
14670
14743
|
endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
|
|
@@ -14781,7 +14854,7 @@ const EOMONTH = {
|
|
|
14781
14854
|
const _months = Math.trunc(toNumber(months, this.locale));
|
|
14782
14855
|
const yStart = _startDate.getFullYear();
|
|
14783
14856
|
const mStart = _startDate.getMonth();
|
|
14784
|
-
const jsDate = new
|
|
14857
|
+
const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
|
|
14785
14858
|
return jsDateToRoundNumber(jsDate);
|
|
14786
14859
|
},
|
|
14787
14860
|
isExported: true,
|
|
@@ -14818,17 +14891,17 @@ const ISOWEEKNUM = {
|
|
|
14818
14891
|
// The first week of the year is the week that contains the first
|
|
14819
14892
|
// Thursday of the year.
|
|
14820
14893
|
let firstThursday = 1;
|
|
14821
|
-
while (new
|
|
14894
|
+
while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
|
|
14822
14895
|
firstThursday += 1;
|
|
14823
14896
|
}
|
|
14824
|
-
const firstDayOfFirstWeek = new
|
|
14897
|
+
const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
|
|
14825
14898
|
// The last week of the year is the week that contains the last Thursday of
|
|
14826
14899
|
// the year.
|
|
14827
14900
|
let lastThursday = 31;
|
|
14828
|
-
while (new
|
|
14901
|
+
while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
|
|
14829
14902
|
lastThursday -= 1;
|
|
14830
14903
|
}
|
|
14831
|
-
const lastDayOfLastWeek = new
|
|
14904
|
+
const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
|
|
14832
14905
|
// B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
|
|
14833
14906
|
// If our date < firstDayOfFirstWeek then it's in the weeks of the year before
|
|
14834
14907
|
let offsetYear;
|
|
@@ -14854,17 +14927,17 @@ const ISOWEEKNUM = {
|
|
|
14854
14927
|
case 1:
|
|
14855
14928
|
// firstDay is the 1st day of the 1st week of the year after
|
|
14856
14929
|
// firstDay = lastDayOfLastWeek + 1 Day
|
|
14857
|
-
firstDay = new
|
|
14930
|
+
firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
|
|
14858
14931
|
break;
|
|
14859
14932
|
case -1:
|
|
14860
14933
|
// firstDay is the 1st day of the 1st week of the previous year.
|
|
14861
14934
|
// The first week of the previous year is the week that contains the
|
|
14862
14935
|
// first Thursday of the previous year.
|
|
14863
14936
|
let firstThursdayPreviousYear = 1;
|
|
14864
|
-
while (new
|
|
14937
|
+
while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
|
|
14865
14938
|
firstThursdayPreviousYear += 1;
|
|
14866
14939
|
}
|
|
14867
|
-
firstDay = new
|
|
14940
|
+
firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
|
|
14868
14941
|
break;
|
|
14869
14942
|
}
|
|
14870
14943
|
const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
|
|
@@ -14999,8 +15072,8 @@ const NETWORKDAYS_INTL = {
|
|
|
14999
15072
|
});
|
|
15000
15073
|
}
|
|
15001
15074
|
const invertDate = _startDate.getTime() > _endDate.getTime();
|
|
15002
|
-
const stopDate =
|
|
15003
|
-
let stepDate =
|
|
15075
|
+
const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
|
|
15076
|
+
let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
|
|
15004
15077
|
const timeStopDate = stopDate.getTime();
|
|
15005
15078
|
let timeStepDate = stepDate.getTime();
|
|
15006
15079
|
let netWorkingDay = 0;
|
|
@@ -15026,8 +15099,7 @@ const NOW = {
|
|
|
15026
15099
|
return getDateTimeFormat(this.locale);
|
|
15027
15100
|
},
|
|
15028
15101
|
compute: function () {
|
|
15029
|
-
let today =
|
|
15030
|
-
today.setMilliseconds(0);
|
|
15102
|
+
let today = DateTime.now();
|
|
15031
15103
|
const delta = today.getTime() - INITIAL_1900_DAY.getTime();
|
|
15032
15104
|
const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
|
|
15033
15105
|
return Math.floor(delta / MS_PER_DAY) + time;
|
|
@@ -15101,8 +15173,8 @@ const TODAY = {
|
|
|
15101
15173
|
return this.locale.dateFormat;
|
|
15102
15174
|
},
|
|
15103
15175
|
compute: function () {
|
|
15104
|
-
const today =
|
|
15105
|
-
const jsDate = new
|
|
15176
|
+
const today = DateTime.now();
|
|
15177
|
+
const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
|
|
15106
15178
|
return jsDateToRoundNumber(jsDate);
|
|
15107
15179
|
},
|
|
15108
15180
|
isExported: true,
|
|
@@ -15157,10 +15229,10 @@ const WEEKNUM = {
|
|
|
15157
15229
|
}
|
|
15158
15230
|
const y = _date.getFullYear();
|
|
15159
15231
|
let dayStart = 1;
|
|
15160
|
-
let startDayOfFirstWeek = new
|
|
15232
|
+
let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
|
|
15161
15233
|
while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
|
|
15162
15234
|
dayStart += 1;
|
|
15163
|
-
startDayOfFirstWeek = new
|
|
15235
|
+
startDayOfFirstWeek = new DateTime(y, 0, dayStart);
|
|
15164
15236
|
}
|
|
15165
15237
|
const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
|
|
15166
15238
|
if (dif < 0) {
|
|
@@ -15218,7 +15290,7 @@ const WORKDAY_INTL = {
|
|
|
15218
15290
|
timesHoliday.add(holiday.getTime());
|
|
15219
15291
|
});
|
|
15220
15292
|
}
|
|
15221
|
-
let stepDate =
|
|
15293
|
+
let stepDate = DateTime.fromTimestamp(_startDate.getTime());
|
|
15222
15294
|
let timeStepDate = stepDate.getTime();
|
|
15223
15295
|
const unitDay = Math.sign(_numDays);
|
|
15224
15296
|
let stepDay = Math.abs(_numDays);
|
|
@@ -15282,7 +15354,7 @@ const MONTH_START = {
|
|
|
15282
15354
|
const _startDate = toJsDate(date, this.locale);
|
|
15283
15355
|
const yStart = _startDate.getFullYear();
|
|
15284
15356
|
const mStart = _startDate.getMonth();
|
|
15285
|
-
const jsDate = new
|
|
15357
|
+
const jsDate = new DateTime(yStart, mStart, 1);
|
|
15286
15358
|
return jsDateToRoundNumber(jsDate);
|
|
15287
15359
|
},
|
|
15288
15360
|
};
|
|
@@ -15324,7 +15396,7 @@ const QUARTER_START = {
|
|
|
15324
15396
|
compute: function (date) {
|
|
15325
15397
|
const quarter = QUARTER.compute.bind(this)(date);
|
|
15326
15398
|
const year = YEAR.compute.bind(this)(date);
|
|
15327
|
-
const jsDate = new
|
|
15399
|
+
const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
|
|
15328
15400
|
return jsDateToRoundNumber(jsDate);
|
|
15329
15401
|
},
|
|
15330
15402
|
};
|
|
@@ -15341,7 +15413,7 @@ const QUARTER_END = {
|
|
|
15341
15413
|
compute: function (date) {
|
|
15342
15414
|
const quarter = QUARTER.compute.bind(this)(date);
|
|
15343
15415
|
const year = YEAR.compute.bind(this)(date);
|
|
15344
|
-
const jsDate = new
|
|
15416
|
+
const jsDate = new DateTime(year, quarter * 3, 0);
|
|
15345
15417
|
return jsDateToRoundNumber(jsDate);
|
|
15346
15418
|
},
|
|
15347
15419
|
};
|
|
@@ -15357,7 +15429,7 @@ const YEAR_START = {
|
|
|
15357
15429
|
},
|
|
15358
15430
|
compute: function (date) {
|
|
15359
15431
|
const year = YEAR.compute.bind(this)(date);
|
|
15360
|
-
const jsDate = new
|
|
15432
|
+
const jsDate = new DateTime(year, 0, 1);
|
|
15361
15433
|
return jsDateToRoundNumber(jsDate);
|
|
15362
15434
|
},
|
|
15363
15435
|
};
|
|
@@ -15373,7 +15445,7 @@ const YEAR_END = {
|
|
|
15373
15445
|
},
|
|
15374
15446
|
compute: function (date) {
|
|
15375
15447
|
const year = YEAR.compute.bind(this)(date);
|
|
15376
|
-
const jsDate = new
|
|
15448
|
+
const jsDate = new DateTime(year + 1, 0, 0);
|
|
15377
15449
|
return jsDateToRoundNumber(jsDate);
|
|
15378
15450
|
},
|
|
15379
15451
|
};
|
|
@@ -15421,8 +15493,8 @@ const DEFAULT_DELTA_ARG = 0;
|
|
|
15421
15493
|
const DELTA = {
|
|
15422
15494
|
description: _t("Compare two numeric values, returning 1 if they're equal."),
|
|
15423
15495
|
args: [
|
|
15424
|
-
arg(" (number)", _t("The first number to compare.")),
|
|
15425
|
-
arg(` (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
|
|
15496
|
+
arg("number1 (number)", _t("The first number to compare.")),
|
|
15497
|
+
arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
|
|
15426
15498
|
],
|
|
15427
15499
|
returns: ["NUMBER"],
|
|
15428
15500
|
compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
|
|
@@ -15849,7 +15921,7 @@ function assertDeprecationFactorStrictlyPositive(factor) {
|
|
|
15849
15921
|
function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity, locale) {
|
|
15850
15922
|
const startDate = toJsDate(settlement, locale);
|
|
15851
15923
|
const endDate = toJsDate(maturity, locale);
|
|
15852
|
-
const startDatePlusOneYear =
|
|
15924
|
+
const startDatePlusOneYear = toJsDate(settlement, locale);
|
|
15853
15925
|
startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
|
|
15854
15926
|
assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _t("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
|
|
15855
15927
|
}
|
|
@@ -15984,7 +16056,7 @@ const AMORLINC = {
|
|
|
15984
16056
|
arg("salvage (number)", _t("The value of the asset at the end of depreciation.")),
|
|
15985
16057
|
arg("period (number)", _t("The single period within life for which to calculate depreciation.")),
|
|
15986
16058
|
arg("rate (number)", _t("The deprecation rate.")),
|
|
15987
|
-
arg(" (number, optional)", _t("An indicator of what day count method to use.")),
|
|
16059
|
+
arg("day_count_convention (number, optional)", _t("An indicator of what day count method to use.")),
|
|
15988
16060
|
],
|
|
15989
16061
|
returns: ["NUMBER"],
|
|
15990
16062
|
compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
|
|
@@ -19008,8 +19080,8 @@ const MID = {
|
|
|
19008
19080
|
description: _t("A segment of a string."),
|
|
19009
19081
|
args: [
|
|
19010
19082
|
arg("text (string)", _t("The string to extract a segment from.")),
|
|
19011
|
-
arg(" (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
|
|
19012
|
-
arg(" (number)", _t("The length of the segment to extract.")),
|
|
19083
|
+
arg("starting_at (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
|
|
19084
|
+
arg("extract_length (number)", _t("The length of the segment to extract.")),
|
|
19013
19085
|
],
|
|
19014
19086
|
returns: ["STRING"],
|
|
19015
19087
|
compute: function (text, starting_at, extract_length) {
|
|
@@ -21212,10 +21284,10 @@ const arrowMap = {
|
|
|
21212
21284
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21213
21285
|
const direction = arrowMap[ev.key];
|
|
21214
21286
|
if (ev.shiftKey) {
|
|
21215
|
-
selection.resizeAnchorZone(direction, ev
|
|
21287
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21216
21288
|
}
|
|
21217
21289
|
else {
|
|
21218
|
-
selection.moveAnchorCell(direction, ev
|
|
21290
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21219
21291
|
}
|
|
21220
21292
|
}
|
|
21221
21293
|
|
|
@@ -24203,7 +24275,7 @@ dataValidationEvaluatorRegistry.add("dateIs", {
|
|
|
24203
24275
|
return false;
|
|
24204
24276
|
}
|
|
24205
24277
|
if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
|
|
24206
|
-
const today = jsDateToRoundNumber(
|
|
24278
|
+
const today = jsDateToRoundNumber(DateTime.now());
|
|
24207
24279
|
return isDateBetween(dateValue, today, criterionValue);
|
|
24208
24280
|
}
|
|
24209
24281
|
return areDatesSameDay(dateValue, criterionValue);
|
|
@@ -28200,7 +28272,10 @@ class GridOverlay extends Component {
|
|
|
28200
28272
|
return;
|
|
28201
28273
|
}
|
|
28202
28274
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28203
|
-
this.props.onCellClicked(col, row, {
|
|
28275
|
+
this.props.onCellClicked(col, row, {
|
|
28276
|
+
expandZone: ev.shiftKey,
|
|
28277
|
+
addZone: isCtrlKey(ev),
|
|
28278
|
+
});
|
|
28204
28279
|
}
|
|
28205
28280
|
onDoubleClick(ev) {
|
|
28206
28281
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28430,7 +28505,7 @@ class AbstractResizer extends Component {
|
|
|
28430
28505
|
this._increaseSelection(index);
|
|
28431
28506
|
}
|
|
28432
28507
|
else {
|
|
28433
|
-
this._selectElement(index, ev
|
|
28508
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28434
28509
|
}
|
|
28435
28510
|
this.lastSelectedElementIndex = index;
|
|
28436
28511
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28594,8 +28669,8 @@ class ColResizer extends AbstractResizer {
|
|
|
28594
28669
|
this.env.raiseError(MergeErrorMessage);
|
|
28595
28670
|
}
|
|
28596
28671
|
}
|
|
28597
|
-
_selectElement(index,
|
|
28598
|
-
this.env.model.selection.selectColumn(index,
|
|
28672
|
+
_selectElement(index, addDistinctHeader) {
|
|
28673
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28599
28674
|
}
|
|
28600
28675
|
_increaseSelection(index) {
|
|
28601
28676
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -28768,8 +28843,8 @@ class RowResizer extends AbstractResizer {
|
|
|
28768
28843
|
this.env.raiseError(MergeErrorMessage);
|
|
28769
28844
|
}
|
|
28770
28845
|
}
|
|
28771
|
-
_selectElement(index,
|
|
28772
|
-
this.env.model.selection.selectRow(index,
|
|
28846
|
+
_selectElement(index, addDistinctHeader) {
|
|
28847
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28773
28848
|
}
|
|
28774
28849
|
_increaseSelection(index) {
|
|
28775
28850
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -29571,17 +29646,17 @@ class Grid extends Component {
|
|
|
29571
29646
|
// ---------------------------------------------------------------------------
|
|
29572
29647
|
// Zone selection with mouse
|
|
29573
29648
|
// ---------------------------------------------------------------------------
|
|
29574
|
-
onCellClicked(col, row, {
|
|
29649
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29575
29650
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29576
29651
|
this.closeOpenedPopover();
|
|
29577
29652
|
}
|
|
29578
29653
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29579
29654
|
interactiveStopEdition(this.env);
|
|
29580
29655
|
}
|
|
29581
|
-
if (
|
|
29656
|
+
if (expandZone) {
|
|
29582
29657
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29583
29658
|
}
|
|
29584
|
-
else if (
|
|
29659
|
+
else if (addZone) {
|
|
29585
29660
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29586
29661
|
}
|
|
29587
29662
|
else {
|
|
@@ -29642,7 +29717,7 @@ class Grid extends Component {
|
|
|
29642
29717
|
onKeydown(ev) {
|
|
29643
29718
|
const eventKey = ev.key.toUpperCase();
|
|
29644
29719
|
let keyDownString = "";
|
|
29645
|
-
if (ev
|
|
29720
|
+
if (isCtrlKey(ev) && eventKey !== "CTRL")
|
|
29646
29721
|
keyDownString += "CTRL+";
|
|
29647
29722
|
if (ev.metaKey)
|
|
29648
29723
|
keyDownString += "CTRL+";
|
|
@@ -34533,7 +34608,7 @@ class BordersPlugin extends CorePlugin {
|
|
|
34533
34608
|
*/
|
|
34534
34609
|
function getBorderId(border) {
|
|
34535
34610
|
for (let [key, value] of Object.entries(borders)) {
|
|
34536
|
-
if (
|
|
34611
|
+
if (deepEquals(value, border)) {
|
|
34537
34612
|
return parseInt(key, 10);
|
|
34538
34613
|
}
|
|
34539
34614
|
}
|
|
@@ -39852,56 +39927,758 @@ class CompilationParametersBuilder {
|
|
|
39852
39927
|
}
|
|
39853
39928
|
}
|
|
39854
39929
|
|
|
39930
|
+
function quickselect(arr, k, left, right, compare) {
|
|
39931
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
39932
|
+
}
|
|
39933
|
+
|
|
39934
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
39935
|
+
|
|
39936
|
+
while (right > left) {
|
|
39937
|
+
if (right - left > 600) {
|
|
39938
|
+
var n = right - left + 1;
|
|
39939
|
+
var m = k - left + 1;
|
|
39940
|
+
var z = Math.log(n);
|
|
39941
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
39942
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
39943
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
39944
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
39945
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
39946
|
+
}
|
|
39947
|
+
|
|
39948
|
+
var t = arr[k];
|
|
39949
|
+
var i = left;
|
|
39950
|
+
var j = right;
|
|
39951
|
+
|
|
39952
|
+
swap(arr, left, k);
|
|
39953
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
39954
|
+
|
|
39955
|
+
while (i < j) {
|
|
39956
|
+
swap(arr, i, j);
|
|
39957
|
+
i++;
|
|
39958
|
+
j--;
|
|
39959
|
+
while (compare(arr[i], t) < 0) i++;
|
|
39960
|
+
while (compare(arr[j], t) > 0) j--;
|
|
39961
|
+
}
|
|
39962
|
+
|
|
39963
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
39964
|
+
else {
|
|
39965
|
+
j++;
|
|
39966
|
+
swap(arr, j, right);
|
|
39967
|
+
}
|
|
39968
|
+
|
|
39969
|
+
if (j <= k) left = j + 1;
|
|
39970
|
+
if (k <= j) right = j - 1;
|
|
39971
|
+
}
|
|
39972
|
+
}
|
|
39973
|
+
|
|
39974
|
+
function swap(arr, i, j) {
|
|
39975
|
+
var tmp = arr[i];
|
|
39976
|
+
arr[i] = arr[j];
|
|
39977
|
+
arr[j] = tmp;
|
|
39978
|
+
}
|
|
39979
|
+
|
|
39980
|
+
function defaultCompare(a, b) {
|
|
39981
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
39982
|
+
}
|
|
39983
|
+
|
|
39984
|
+
class RBush {
|
|
39985
|
+
constructor(maxEntries = 9) {
|
|
39986
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
39987
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
39988
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
39989
|
+
this.clear();
|
|
39990
|
+
}
|
|
39991
|
+
|
|
39992
|
+
all() {
|
|
39993
|
+
return this._all(this.data, []);
|
|
39994
|
+
}
|
|
39995
|
+
|
|
39996
|
+
search(bbox) {
|
|
39997
|
+
let node = this.data;
|
|
39998
|
+
const result = [];
|
|
39999
|
+
|
|
40000
|
+
if (!intersects(bbox, node)) return result;
|
|
40001
|
+
|
|
40002
|
+
const toBBox = this.toBBox;
|
|
40003
|
+
const nodesToSearch = [];
|
|
40004
|
+
|
|
40005
|
+
while (node) {
|
|
40006
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40007
|
+
const child = node.children[i];
|
|
40008
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
40009
|
+
|
|
40010
|
+
if (intersects(bbox, childBBox)) {
|
|
40011
|
+
if (node.leaf) result.push(child);
|
|
40012
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
40013
|
+
else nodesToSearch.push(child);
|
|
40014
|
+
}
|
|
40015
|
+
}
|
|
40016
|
+
node = nodesToSearch.pop();
|
|
40017
|
+
}
|
|
40018
|
+
|
|
40019
|
+
return result;
|
|
40020
|
+
}
|
|
40021
|
+
|
|
40022
|
+
collides(bbox) {
|
|
40023
|
+
let node = this.data;
|
|
40024
|
+
|
|
40025
|
+
if (!intersects(bbox, node)) return false;
|
|
40026
|
+
|
|
40027
|
+
const nodesToSearch = [];
|
|
40028
|
+
while (node) {
|
|
40029
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40030
|
+
const child = node.children[i];
|
|
40031
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
40032
|
+
|
|
40033
|
+
if (intersects(bbox, childBBox)) {
|
|
40034
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
40035
|
+
nodesToSearch.push(child);
|
|
40036
|
+
}
|
|
40037
|
+
}
|
|
40038
|
+
node = nodesToSearch.pop();
|
|
40039
|
+
}
|
|
40040
|
+
|
|
40041
|
+
return false;
|
|
40042
|
+
}
|
|
40043
|
+
|
|
40044
|
+
load(data) {
|
|
40045
|
+
if (!(data && data.length)) return this;
|
|
40046
|
+
|
|
40047
|
+
if (data.length < this._minEntries) {
|
|
40048
|
+
for (let i = 0; i < data.length; i++) {
|
|
40049
|
+
this.insert(data[i]);
|
|
40050
|
+
}
|
|
40051
|
+
return this;
|
|
40052
|
+
}
|
|
40053
|
+
|
|
40054
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
40055
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
40056
|
+
|
|
40057
|
+
if (!this.data.children.length) {
|
|
40058
|
+
// save as is if tree is empty
|
|
40059
|
+
this.data = node;
|
|
40060
|
+
|
|
40061
|
+
} else if (this.data.height === node.height) {
|
|
40062
|
+
// split root if trees have the same height
|
|
40063
|
+
this._splitRoot(this.data, node);
|
|
40064
|
+
|
|
40065
|
+
} else {
|
|
40066
|
+
if (this.data.height < node.height) {
|
|
40067
|
+
// swap trees if inserted one is bigger
|
|
40068
|
+
const tmpNode = this.data;
|
|
40069
|
+
this.data = node;
|
|
40070
|
+
node = tmpNode;
|
|
40071
|
+
}
|
|
40072
|
+
|
|
40073
|
+
// insert the small tree into the large tree at appropriate level
|
|
40074
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
40075
|
+
}
|
|
40076
|
+
|
|
40077
|
+
return this;
|
|
40078
|
+
}
|
|
40079
|
+
|
|
40080
|
+
insert(item) {
|
|
40081
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
40082
|
+
return this;
|
|
40083
|
+
}
|
|
40084
|
+
|
|
40085
|
+
clear() {
|
|
40086
|
+
this.data = createNode([]);
|
|
40087
|
+
return this;
|
|
40088
|
+
}
|
|
40089
|
+
|
|
40090
|
+
remove(item, equalsFn) {
|
|
40091
|
+
if (!item) return this;
|
|
40092
|
+
|
|
40093
|
+
let node = this.data;
|
|
40094
|
+
const bbox = this.toBBox(item);
|
|
40095
|
+
const path = [];
|
|
40096
|
+
const indexes = [];
|
|
40097
|
+
let i, parent, goingUp;
|
|
40098
|
+
|
|
40099
|
+
// depth-first iterative tree traversal
|
|
40100
|
+
while (node || path.length) {
|
|
40101
|
+
|
|
40102
|
+
if (!node) { // go up
|
|
40103
|
+
node = path.pop();
|
|
40104
|
+
parent = path[path.length - 1];
|
|
40105
|
+
i = indexes.pop();
|
|
40106
|
+
goingUp = true;
|
|
40107
|
+
}
|
|
40108
|
+
|
|
40109
|
+
if (node.leaf) { // check current node
|
|
40110
|
+
const index = findItem(item, node.children, equalsFn);
|
|
40111
|
+
|
|
40112
|
+
if (index !== -1) {
|
|
40113
|
+
// item found, remove the item and condense tree upwards
|
|
40114
|
+
node.children.splice(index, 1);
|
|
40115
|
+
path.push(node);
|
|
40116
|
+
this._condense(path);
|
|
40117
|
+
return this;
|
|
40118
|
+
}
|
|
40119
|
+
}
|
|
40120
|
+
|
|
40121
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
40122
|
+
path.push(node);
|
|
40123
|
+
indexes.push(i);
|
|
40124
|
+
i = 0;
|
|
40125
|
+
parent = node;
|
|
40126
|
+
node = node.children[0];
|
|
40127
|
+
|
|
40128
|
+
} else if (parent) { // go right
|
|
40129
|
+
i++;
|
|
40130
|
+
node = parent.children[i];
|
|
40131
|
+
goingUp = false;
|
|
40132
|
+
|
|
40133
|
+
} else node = null; // nothing found
|
|
40134
|
+
}
|
|
40135
|
+
|
|
40136
|
+
return this;
|
|
40137
|
+
}
|
|
40138
|
+
|
|
40139
|
+
toBBox(item) { return item; }
|
|
40140
|
+
|
|
40141
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
40142
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
40143
|
+
|
|
40144
|
+
toJSON() { return this.data; }
|
|
40145
|
+
|
|
40146
|
+
fromJSON(data) {
|
|
40147
|
+
this.data = data;
|
|
40148
|
+
return this;
|
|
40149
|
+
}
|
|
40150
|
+
|
|
40151
|
+
_all(node, result) {
|
|
40152
|
+
const nodesToSearch = [];
|
|
40153
|
+
while (node) {
|
|
40154
|
+
if (node.leaf) result.push(...node.children);
|
|
40155
|
+
else nodesToSearch.push(...node.children);
|
|
40156
|
+
|
|
40157
|
+
node = nodesToSearch.pop();
|
|
40158
|
+
}
|
|
40159
|
+
return result;
|
|
40160
|
+
}
|
|
40161
|
+
|
|
40162
|
+
_build(items, left, right, height) {
|
|
40163
|
+
|
|
40164
|
+
const N = right - left + 1;
|
|
40165
|
+
let M = this._maxEntries;
|
|
40166
|
+
let node;
|
|
40167
|
+
|
|
40168
|
+
if (N <= M) {
|
|
40169
|
+
// reached leaf level; return leaf
|
|
40170
|
+
node = createNode(items.slice(left, right + 1));
|
|
40171
|
+
calcBBox(node, this.toBBox);
|
|
40172
|
+
return node;
|
|
40173
|
+
}
|
|
40174
|
+
|
|
40175
|
+
if (!height) {
|
|
40176
|
+
// target height of the bulk-loaded tree
|
|
40177
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
40178
|
+
|
|
40179
|
+
// target number of root entries to maximize storage utilization
|
|
40180
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
40181
|
+
}
|
|
40182
|
+
|
|
40183
|
+
node = createNode([]);
|
|
40184
|
+
node.leaf = false;
|
|
40185
|
+
node.height = height;
|
|
40186
|
+
|
|
40187
|
+
// split the items into M mostly square tiles
|
|
40188
|
+
|
|
40189
|
+
const N2 = Math.ceil(N / M);
|
|
40190
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
40191
|
+
|
|
40192
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
40193
|
+
|
|
40194
|
+
for (let i = left; i <= right; i += N1) {
|
|
40195
|
+
|
|
40196
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
40197
|
+
|
|
40198
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
40199
|
+
|
|
40200
|
+
for (let j = i; j <= right2; j += N2) {
|
|
40201
|
+
|
|
40202
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
40203
|
+
|
|
40204
|
+
// pack each entry recursively
|
|
40205
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
40206
|
+
}
|
|
40207
|
+
}
|
|
40208
|
+
|
|
40209
|
+
calcBBox(node, this.toBBox);
|
|
40210
|
+
|
|
40211
|
+
return node;
|
|
40212
|
+
}
|
|
40213
|
+
|
|
40214
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
40215
|
+
while (true) {
|
|
40216
|
+
path.push(node);
|
|
40217
|
+
|
|
40218
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
40219
|
+
|
|
40220
|
+
let minArea = Infinity;
|
|
40221
|
+
let minEnlargement = Infinity;
|
|
40222
|
+
let targetNode;
|
|
40223
|
+
|
|
40224
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40225
|
+
const child = node.children[i];
|
|
40226
|
+
const area = bboxArea(child);
|
|
40227
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
40228
|
+
|
|
40229
|
+
// choose entry with the least area enlargement
|
|
40230
|
+
if (enlargement < minEnlargement) {
|
|
40231
|
+
minEnlargement = enlargement;
|
|
40232
|
+
minArea = area < minArea ? area : minArea;
|
|
40233
|
+
targetNode = child;
|
|
40234
|
+
|
|
40235
|
+
} else if (enlargement === minEnlargement) {
|
|
40236
|
+
// otherwise choose one with the smallest area
|
|
40237
|
+
if (area < minArea) {
|
|
40238
|
+
minArea = area;
|
|
40239
|
+
targetNode = child;
|
|
40240
|
+
}
|
|
40241
|
+
}
|
|
40242
|
+
}
|
|
40243
|
+
|
|
40244
|
+
node = targetNode || node.children[0];
|
|
40245
|
+
}
|
|
40246
|
+
|
|
40247
|
+
return node;
|
|
40248
|
+
}
|
|
40249
|
+
|
|
40250
|
+
_insert(item, level, isNode) {
|
|
40251
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
40252
|
+
const insertPath = [];
|
|
40253
|
+
|
|
40254
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
40255
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
40256
|
+
|
|
40257
|
+
// put the item into the node
|
|
40258
|
+
node.children.push(item);
|
|
40259
|
+
extend(node, bbox);
|
|
40260
|
+
|
|
40261
|
+
// split on node overflow; propagate upwards if necessary
|
|
40262
|
+
while (level >= 0) {
|
|
40263
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
40264
|
+
this._split(insertPath, level);
|
|
40265
|
+
level--;
|
|
40266
|
+
} else break;
|
|
40267
|
+
}
|
|
40268
|
+
|
|
40269
|
+
// adjust bboxes along the insertion path
|
|
40270
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
40271
|
+
}
|
|
40272
|
+
|
|
40273
|
+
// split overflowed node into two
|
|
40274
|
+
_split(insertPath, level) {
|
|
40275
|
+
const node = insertPath[level];
|
|
40276
|
+
const M = node.children.length;
|
|
40277
|
+
const m = this._minEntries;
|
|
40278
|
+
|
|
40279
|
+
this._chooseSplitAxis(node, m, M);
|
|
40280
|
+
|
|
40281
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
40282
|
+
|
|
40283
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
40284
|
+
newNode.height = node.height;
|
|
40285
|
+
newNode.leaf = node.leaf;
|
|
40286
|
+
|
|
40287
|
+
calcBBox(node, this.toBBox);
|
|
40288
|
+
calcBBox(newNode, this.toBBox);
|
|
40289
|
+
|
|
40290
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
40291
|
+
else this._splitRoot(node, newNode);
|
|
40292
|
+
}
|
|
40293
|
+
|
|
40294
|
+
_splitRoot(node, newNode) {
|
|
40295
|
+
// split root node
|
|
40296
|
+
this.data = createNode([node, newNode]);
|
|
40297
|
+
this.data.height = node.height + 1;
|
|
40298
|
+
this.data.leaf = false;
|
|
40299
|
+
calcBBox(this.data, this.toBBox);
|
|
40300
|
+
}
|
|
40301
|
+
|
|
40302
|
+
_chooseSplitIndex(node, m, M) {
|
|
40303
|
+
let index;
|
|
40304
|
+
let minOverlap = Infinity;
|
|
40305
|
+
let minArea = Infinity;
|
|
40306
|
+
|
|
40307
|
+
for (let i = m; i <= M - m; i++) {
|
|
40308
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
40309
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
40310
|
+
|
|
40311
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
40312
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
40313
|
+
|
|
40314
|
+
// choose distribution with minimum overlap
|
|
40315
|
+
if (overlap < minOverlap) {
|
|
40316
|
+
minOverlap = overlap;
|
|
40317
|
+
index = i;
|
|
40318
|
+
|
|
40319
|
+
minArea = area < minArea ? area : minArea;
|
|
40320
|
+
|
|
40321
|
+
} else if (overlap === minOverlap) {
|
|
40322
|
+
// otherwise choose distribution with minimum area
|
|
40323
|
+
if (area < minArea) {
|
|
40324
|
+
minArea = area;
|
|
40325
|
+
index = i;
|
|
40326
|
+
}
|
|
40327
|
+
}
|
|
40328
|
+
}
|
|
40329
|
+
|
|
40330
|
+
return index || M - m;
|
|
40331
|
+
}
|
|
40332
|
+
|
|
40333
|
+
// sorts node children by the best axis for split
|
|
40334
|
+
_chooseSplitAxis(node, m, M) {
|
|
40335
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
40336
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
40337
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
40338
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
40339
|
+
|
|
40340
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
40341
|
+
// otherwise it's already sorted by minY
|
|
40342
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
40343
|
+
}
|
|
40344
|
+
|
|
40345
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
40346
|
+
_allDistMargin(node, m, M, compare) {
|
|
40347
|
+
node.children.sort(compare);
|
|
40348
|
+
|
|
40349
|
+
const toBBox = this.toBBox;
|
|
40350
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
40351
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
40352
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
40353
|
+
|
|
40354
|
+
for (let i = m; i < M - m; i++) {
|
|
40355
|
+
const child = node.children[i];
|
|
40356
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
40357
|
+
margin += bboxMargin(leftBBox);
|
|
40358
|
+
}
|
|
40359
|
+
|
|
40360
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
40361
|
+
const child = node.children[i];
|
|
40362
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
40363
|
+
margin += bboxMargin(rightBBox);
|
|
40364
|
+
}
|
|
40365
|
+
|
|
40366
|
+
return margin;
|
|
40367
|
+
}
|
|
40368
|
+
|
|
40369
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
40370
|
+
// adjust bboxes along the given tree path
|
|
40371
|
+
for (let i = level; i >= 0; i--) {
|
|
40372
|
+
extend(path[i], bbox);
|
|
40373
|
+
}
|
|
40374
|
+
}
|
|
40375
|
+
|
|
40376
|
+
_condense(path) {
|
|
40377
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
40378
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
40379
|
+
if (path[i].children.length === 0) {
|
|
40380
|
+
if (i > 0) {
|
|
40381
|
+
siblings = path[i - 1].children;
|
|
40382
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
40383
|
+
|
|
40384
|
+
} else this.clear();
|
|
40385
|
+
|
|
40386
|
+
} else calcBBox(path[i], this.toBBox);
|
|
40387
|
+
}
|
|
40388
|
+
}
|
|
40389
|
+
}
|
|
40390
|
+
|
|
40391
|
+
function findItem(item, items, equalsFn) {
|
|
40392
|
+
if (!equalsFn) return items.indexOf(item);
|
|
40393
|
+
|
|
40394
|
+
for (let i = 0; i < items.length; i++) {
|
|
40395
|
+
if (equalsFn(item, items[i])) return i;
|
|
40396
|
+
}
|
|
40397
|
+
return -1;
|
|
40398
|
+
}
|
|
40399
|
+
|
|
40400
|
+
// calculate node's bbox from bboxes of its children
|
|
40401
|
+
function calcBBox(node, toBBox) {
|
|
40402
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
40403
|
+
}
|
|
40404
|
+
|
|
40405
|
+
// min bounding rectangle of node children from k to p-1
|
|
40406
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
40407
|
+
if (!destNode) destNode = createNode(null);
|
|
40408
|
+
destNode.minX = Infinity;
|
|
40409
|
+
destNode.minY = Infinity;
|
|
40410
|
+
destNode.maxX = -Infinity;
|
|
40411
|
+
destNode.maxY = -Infinity;
|
|
40412
|
+
|
|
40413
|
+
for (let i = k; i < p; i++) {
|
|
40414
|
+
const child = node.children[i];
|
|
40415
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
40416
|
+
}
|
|
40417
|
+
|
|
40418
|
+
return destNode;
|
|
40419
|
+
}
|
|
40420
|
+
|
|
40421
|
+
function extend(a, b) {
|
|
40422
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
40423
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
40424
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
40425
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
40426
|
+
return a;
|
|
40427
|
+
}
|
|
40428
|
+
|
|
40429
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
40430
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
40431
|
+
|
|
40432
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
40433
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
40434
|
+
|
|
40435
|
+
function enlargedArea(a, b) {
|
|
40436
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
40437
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
40438
|
+
}
|
|
40439
|
+
|
|
40440
|
+
function intersectionArea(a, b) {
|
|
40441
|
+
const minX = Math.max(a.minX, b.minX);
|
|
40442
|
+
const minY = Math.max(a.minY, b.minY);
|
|
40443
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
40444
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
40445
|
+
|
|
40446
|
+
return Math.max(0, maxX - minX) *
|
|
40447
|
+
Math.max(0, maxY - minY);
|
|
40448
|
+
}
|
|
40449
|
+
|
|
40450
|
+
function contains(a, b) {
|
|
40451
|
+
return a.minX <= b.minX &&
|
|
40452
|
+
a.minY <= b.minY &&
|
|
40453
|
+
b.maxX <= a.maxX &&
|
|
40454
|
+
b.maxY <= a.maxY;
|
|
40455
|
+
}
|
|
40456
|
+
|
|
40457
|
+
function intersects(a, b) {
|
|
40458
|
+
return b.minX <= a.maxX &&
|
|
40459
|
+
b.minY <= a.maxY &&
|
|
40460
|
+
b.maxX >= a.minX &&
|
|
40461
|
+
b.maxY >= a.minY;
|
|
40462
|
+
}
|
|
40463
|
+
|
|
40464
|
+
function createNode(children) {
|
|
40465
|
+
return {
|
|
40466
|
+
children,
|
|
40467
|
+
height: 1,
|
|
40468
|
+
leaf: true,
|
|
40469
|
+
minX: Infinity,
|
|
40470
|
+
minY: Infinity,
|
|
40471
|
+
maxX: -Infinity,
|
|
40472
|
+
maxY: -Infinity
|
|
40473
|
+
};
|
|
40474
|
+
}
|
|
40475
|
+
|
|
40476
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
40477
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
40478
|
+
|
|
40479
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
40480
|
+
const stack = [left, right];
|
|
40481
|
+
|
|
40482
|
+
while (stack.length) {
|
|
40483
|
+
right = stack.pop();
|
|
40484
|
+
left = stack.pop();
|
|
40485
|
+
|
|
40486
|
+
if (right - left <= n) continue;
|
|
40487
|
+
|
|
40488
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
40489
|
+
quickselect(arr, mid, left, right, compare);
|
|
40490
|
+
|
|
40491
|
+
stack.push(left, mid, mid, right);
|
|
40492
|
+
}
|
|
40493
|
+
}
|
|
40494
|
+
|
|
39855
40495
|
/**
|
|
39856
|
-
*
|
|
40496
|
+
* R-Tree Data Structure
|
|
40497
|
+
*
|
|
40498
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
40499
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
40500
|
+
*
|
|
40501
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
40502
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
40503
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
40504
|
+
* for faster spatial queries.
|
|
40505
|
+
*
|
|
40506
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
40507
|
+
*
|
|
40508
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
40509
|
+
* +--------------------------+
|
|
40510
|
+
* | |
|
|
40511
|
+
* | +---+ +-------+ |
|
|
40512
|
+
* | | A | | B | |
|
|
40513
|
+
* | +---+ +-------+ |
|
|
40514
|
+
* | |
|
|
40515
|
+
* | |
|
|
40516
|
+
* | +---+ |
|
|
40517
|
+
* | | C | |
|
|
40518
|
+
* | +---+ |
|
|
40519
|
+
* | +-----------+ |
|
|
40520
|
+
* | | D | |
|
|
40521
|
+
* | +-----------+ |
|
|
40522
|
+
* | |
|
|
40523
|
+
* +--------------------------+
|
|
40524
|
+
*
|
|
40525
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
40526
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
40527
|
+
* in R2.
|
|
40528
|
+
*
|
|
40529
|
+
* R0
|
|
40530
|
+
* +--------------------------+
|
|
40531
|
+
* | R1 |
|
|
40532
|
+
* | +-----------------+ |
|
|
40533
|
+
* | | A | | B | |
|
|
40534
|
+
* | +-----------------+ |
|
|
40535
|
+
* | |
|
|
40536
|
+
* | R2 |
|
|
40537
|
+
* | +---+---+---+ |
|
|
40538
|
+
* | | | C | | |
|
|
40539
|
+
* | | +---+ | |
|
|
40540
|
+
* | +-----------+ |
|
|
40541
|
+
* | | D | |
|
|
40542
|
+
* | +-----------+ |
|
|
40543
|
+
* | |
|
|
40544
|
+
* +--------------------------+
|
|
40545
|
+
*
|
|
40546
|
+
* The tree would look like this:
|
|
40547
|
+
* R0
|
|
40548
|
+
* / \
|
|
40549
|
+
* / \
|
|
40550
|
+
* R1 R2
|
|
40551
|
+
* | |
|
|
40552
|
+
* A,B C,D
|
|
40553
|
+
|
|
40554
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
40555
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
40556
|
+
* to reduce the number of subtrees processed during searches.
|
|
40557
|
+
*
|
|
40558
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
40559
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
40560
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
40561
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
40562
|
+
*
|
|
40563
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
40564
|
+
* with a zone and a sheet.
|
|
40565
|
+
*
|
|
40566
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
40567
|
+
* @see https://github.com/mourner/rbush
|
|
40568
|
+
*/
|
|
40569
|
+
class SpreadsheetRTree {
|
|
40570
|
+
/**
|
|
40571
|
+
* One 2D R-tree per sheet
|
|
40572
|
+
*/
|
|
40573
|
+
rTrees = {};
|
|
40574
|
+
/**
|
|
40575
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
40576
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
40577
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
40578
|
+
*/
|
|
40579
|
+
constructor(items = []) {
|
|
40580
|
+
const rangesPerSheet = {};
|
|
40581
|
+
for (const item of items) {
|
|
40582
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40583
|
+
if (!rangesPerSheet[sheetId]) {
|
|
40584
|
+
rangesPerSheet[sheetId] = [];
|
|
40585
|
+
}
|
|
40586
|
+
rangesPerSheet[sheetId].push(item);
|
|
40587
|
+
}
|
|
40588
|
+
for (const sheetId in rangesPerSheet) {
|
|
40589
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40590
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
40591
|
+
}
|
|
40592
|
+
}
|
|
40593
|
+
insert(item) {
|
|
40594
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40595
|
+
if (!this.rTrees[sheetId]) {
|
|
40596
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40597
|
+
}
|
|
40598
|
+
this.rTrees[sheetId].insert(item);
|
|
40599
|
+
}
|
|
40600
|
+
search({ zone, sheetId }) {
|
|
40601
|
+
if (!this.rTrees[sheetId]) {
|
|
40602
|
+
return [];
|
|
40603
|
+
}
|
|
40604
|
+
return this.rTrees[sheetId].search({
|
|
40605
|
+
minX: zone.left,
|
|
40606
|
+
minY: zone.top,
|
|
40607
|
+
maxX: zone.right,
|
|
40608
|
+
maxY: zone.bottom,
|
|
40609
|
+
});
|
|
40610
|
+
}
|
|
40611
|
+
remove(item) {
|
|
40612
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40613
|
+
if (!this.rTrees[sheetId]) {
|
|
40614
|
+
return;
|
|
40615
|
+
}
|
|
40616
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
40617
|
+
}
|
|
40618
|
+
}
|
|
40619
|
+
/**
|
|
40620
|
+
* RBush extension to use zones as bounding boxes
|
|
40621
|
+
*/
|
|
40622
|
+
class ZoneRBush extends RBush {
|
|
40623
|
+
toBBox({ boundingBox }) {
|
|
40624
|
+
const zone = boundingBox.zone;
|
|
40625
|
+
return {
|
|
40626
|
+
minX: zone.left,
|
|
40627
|
+
minY: zone.top,
|
|
40628
|
+
maxX: zone.right,
|
|
40629
|
+
maxY: zone.bottom,
|
|
40630
|
+
};
|
|
40631
|
+
}
|
|
40632
|
+
compareMinX(a, b) {
|
|
40633
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
40634
|
+
}
|
|
40635
|
+
compareMinY(a, b) {
|
|
40636
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
40637
|
+
}
|
|
40638
|
+
}
|
|
40639
|
+
|
|
40640
|
+
/**
|
|
40641
|
+
* Implementation of a dependency Graph.
|
|
39857
40642
|
* The graph is used to evaluate the cells in the correct
|
|
39858
40643
|
* order, and should be updated each time a cell's content is modified
|
|
39859
40644
|
*
|
|
40645
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
39860
40646
|
*/
|
|
39861
40647
|
class FormulaDependencyGraph {
|
|
39862
|
-
|
|
39863
|
-
* Internal structure:
|
|
39864
|
-
* - key: a cell position (encoded as an integer)
|
|
39865
|
-
* - value: a set of cell positions that depends on the key
|
|
39866
|
-
*
|
|
39867
|
-
* Given
|
|
39868
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
39869
|
-
* - C1:"= B1";
|
|
39870
|
-
* - C2:"= C1"
|
|
39871
|
-
*
|
|
39872
|
-
* we will have something like:
|
|
39873
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
39874
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
39875
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
39876
|
-
*/
|
|
39877
|
-
inverseDependencies = new Map();
|
|
40648
|
+
encoder;
|
|
39878
40649
|
dependencies = new Map();
|
|
40650
|
+
rTree;
|
|
40651
|
+
constructor(encoder, data = []) {
|
|
40652
|
+
this.encoder = encoder;
|
|
40653
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40654
|
+
}
|
|
39879
40655
|
removeAllDependencies(formulaPositionId) {
|
|
39880
|
-
const
|
|
39881
|
-
if (!
|
|
40656
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40657
|
+
if (!ranges) {
|
|
39882
40658
|
return;
|
|
39883
40659
|
}
|
|
39884
|
-
for (const
|
|
39885
|
-
this.
|
|
40660
|
+
for (const range of ranges) {
|
|
40661
|
+
this.rTree.remove(range);
|
|
39886
40662
|
}
|
|
39887
40663
|
this.dependencies.delete(formulaPositionId);
|
|
39888
40664
|
}
|
|
39889
40665
|
addDependencies(formulaPositionId, dependencies) {
|
|
39890
|
-
|
|
39891
|
-
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
|
|
39895
|
-
|
|
39896
|
-
|
|
39897
|
-
|
|
40666
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
40667
|
+
data: formulaPositionId,
|
|
40668
|
+
boundingBox: {
|
|
40669
|
+
zone,
|
|
40670
|
+
sheetId,
|
|
40671
|
+
},
|
|
40672
|
+
}));
|
|
40673
|
+
for (const item of rTreeItems) {
|
|
40674
|
+
this.rTree.insert(item);
|
|
39898
40675
|
}
|
|
39899
40676
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
39900
40677
|
if (existingDependencies) {
|
|
39901
|
-
existingDependencies.push(...
|
|
40678
|
+
existingDependencies.push(...rTreeItems);
|
|
39902
40679
|
}
|
|
39903
40680
|
else {
|
|
39904
|
-
this.dependencies.set(formulaPositionId,
|
|
40681
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
39905
40682
|
}
|
|
39906
40683
|
}
|
|
39907
40684
|
/**
|
|
@@ -39909,20 +40686,20 @@ class FormulaDependencyGraph {
|
|
|
39909
40686
|
* in the correct order they should be evaluated.
|
|
39910
40687
|
* This is called a topological ordering (excluding cycles)
|
|
39911
40688
|
*/
|
|
39912
|
-
getCellsDependingOn(
|
|
40689
|
+
getCellsDependingOn(ranges) {
|
|
39913
40690
|
const visited = new JetSet();
|
|
39914
|
-
const queue = Array.from(
|
|
40691
|
+
const queue = Array.from(ranges).reverse();
|
|
39915
40692
|
while (queue.length > 0) {
|
|
39916
|
-
const
|
|
39917
|
-
visited.add(
|
|
39918
|
-
const
|
|
39919
|
-
for (const
|
|
39920
|
-
if (!visited.has(
|
|
39921
|
-
queue.push(
|
|
40693
|
+
const range = queue.pop();
|
|
40694
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
40695
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
40696
|
+
for (const positionId of impactedPositionIds) {
|
|
40697
|
+
if (!visited.has(positionId)) {
|
|
40698
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
39922
40699
|
}
|
|
39923
40700
|
}
|
|
39924
40701
|
}
|
|
39925
|
-
visited.delete(...
|
|
40702
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
39926
40703
|
return visited;
|
|
39927
40704
|
}
|
|
39928
40705
|
}
|
|
@@ -40010,9 +40787,9 @@ class Evaluator {
|
|
|
40010
40787
|
context;
|
|
40011
40788
|
getters;
|
|
40012
40789
|
compilationParams;
|
|
40013
|
-
|
|
40790
|
+
encoder = new PositionBitsEncoder();
|
|
40014
40791
|
evaluatedCells = new Map();
|
|
40015
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40792
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40016
40793
|
blockedArrayFormulas = new Set();
|
|
40017
40794
|
spreadingRelations = new SpreadingRelation();
|
|
40018
40795
|
constructor(context, getters) {
|
|
@@ -40021,23 +40798,23 @@ class Evaluator {
|
|
|
40021
40798
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40022
40799
|
}
|
|
40023
40800
|
getEvaluatedCell(position) {
|
|
40024
|
-
return (this.evaluatedCells.get(this.
|
|
40801
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40025
40802
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40026
40803
|
}
|
|
40027
40804
|
getSpreadPositionsOf(position) {
|
|
40028
|
-
const positionId = this.
|
|
40805
|
+
const positionId = this.encoder.encode(position);
|
|
40029
40806
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40030
40807
|
return [];
|
|
40031
40808
|
}
|
|
40032
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
40809
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40033
40810
|
}
|
|
40034
40811
|
getArrayFormulaSpreadingOn(position) {
|
|
40035
|
-
const positionId = this.
|
|
40812
|
+
const positionId = this.encoder.encode(position);
|
|
40036
40813
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40037
|
-
return formulaPosition !== undefined ? this.
|
|
40814
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40038
40815
|
}
|
|
40039
40816
|
getEvaluatedPositions() {
|
|
40040
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
40817
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40041
40818
|
}
|
|
40042
40819
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40043
40820
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40047,7 +40824,7 @@ class Evaluator {
|
|
|
40047
40824
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40048
40825
|
}
|
|
40049
40826
|
updateDependencies(position) {
|
|
40050
|
-
const positionId = this.
|
|
40827
|
+
const positionId = this.encoder.encode(position);
|
|
40051
40828
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40052
40829
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40053
40830
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40057,12 +40834,12 @@ class Evaluator {
|
|
|
40057
40834
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40058
40835
|
}
|
|
40059
40836
|
evaluateCells(positions) {
|
|
40060
|
-
const cells = positions.map(this.
|
|
40837
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40061
40838
|
const cellsToCompute = new JetSet(cells);
|
|
40062
|
-
const
|
|
40839
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40063
40840
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40064
|
-
cellsToCompute.add(...
|
|
40065
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
40841
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
40842
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40066
40843
|
this.evaluate(cellsToCompute);
|
|
40067
40844
|
}
|
|
40068
40845
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40085,12 +40862,14 @@ class Evaluator {
|
|
|
40085
40862
|
this.blockedArrayFormulas = new Set();
|
|
40086
40863
|
this.spreadingRelations = new SpreadingRelation();
|
|
40087
40864
|
this.formulaDependencies = lazy(() => {
|
|
40088
|
-
const
|
|
40089
|
-
|
|
40090
|
-
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40865
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
40866
|
+
data: positionId,
|
|
40867
|
+
boundingBox: {
|
|
40868
|
+
zone: range.zone,
|
|
40869
|
+
sheetId: range.sheetId,
|
|
40870
|
+
},
|
|
40871
|
+
})));
|
|
40872
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
40094
40873
|
});
|
|
40095
40874
|
}
|
|
40096
40875
|
evaluateAllCells() {
|
|
@@ -40112,7 +40891,7 @@ class Evaluator {
|
|
|
40112
40891
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40113
40892
|
const cellIds = this.getters.getCells(sheetId);
|
|
40114
40893
|
for (const cellId in cellIds) {
|
|
40115
|
-
positionIds.add(this.
|
|
40894
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40116
40895
|
}
|
|
40117
40896
|
}
|
|
40118
40897
|
return positionIds;
|
|
@@ -40179,7 +40958,7 @@ class Evaluator {
|
|
|
40179
40958
|
}
|
|
40180
40959
|
}
|
|
40181
40960
|
computeAndSave(position) {
|
|
40182
|
-
const positionId = this.
|
|
40961
|
+
const positionId = this.encoder.encode(position);
|
|
40183
40962
|
const evaluatedCell = this.computeCell(positionId);
|
|
40184
40963
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40185
40964
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40239,15 +41018,15 @@ class Evaluator {
|
|
|
40239
41018
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40240
41019
|
}
|
|
40241
41020
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40242
|
-
const arrayFormulaPositionId = this.
|
|
41021
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40243
41022
|
return (i, j) => {
|
|
40244
41023
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40245
|
-
const resultPositionId = this.
|
|
41024
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40246
41025
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40247
41026
|
};
|
|
40248
41027
|
}
|
|
40249
41028
|
checkCollision({ sheetId, col, row }) {
|
|
40250
|
-
const formulaPositionId = this.
|
|
41029
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40251
41030
|
return (i, j) => {
|
|
40252
41031
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40253
41032
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40268,7 +41047,7 @@ class Evaluator {
|
|
|
40268
41047
|
format: format || matrixResult[i][j]?.format,
|
|
40269
41048
|
locale: this.getters.getLocale(),
|
|
40270
41049
|
});
|
|
40271
|
-
const positionId = this.
|
|
41050
|
+
const positionId = this.encoder.encode(position);
|
|
40272
41051
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40273
41052
|
// check if formula dependencies present in the spread zone
|
|
40274
41053
|
// if so, they need to be recomputed
|
|
@@ -40300,29 +41079,17 @@ class Evaluator {
|
|
|
40300
41079
|
if (!cell?.isFormula) {
|
|
40301
41080
|
return [];
|
|
40302
41081
|
}
|
|
40303
|
-
|
|
40304
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40305
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40306
|
-
continue;
|
|
40307
|
-
}
|
|
40308
|
-
const sheetId = range.sheetId;
|
|
40309
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40310
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40311
|
-
});
|
|
40312
|
-
}
|
|
40313
|
-
return dependencies;
|
|
41082
|
+
return cell.compiledFormula.dependencies;
|
|
40314
41083
|
}
|
|
40315
41084
|
getCellsDependingOn(positionIds) {
|
|
40316
|
-
|
|
41085
|
+
const ranges = [];
|
|
41086
|
+
for (const positionId of positionIds) {
|
|
41087
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41088
|
+
}
|
|
41089
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40317
41090
|
}
|
|
40318
41091
|
getCell(positionId) {
|
|
40319
|
-
return this.getters.getCell(this.
|
|
40320
|
-
}
|
|
40321
|
-
encodePosition(position) {
|
|
40322
|
-
return this.positionEncoder.encode(position);
|
|
40323
|
-
}
|
|
40324
|
-
decodePosition(positionId) {
|
|
40325
|
-
return this.positionEncoder.decode(positionId);
|
|
41092
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40326
41093
|
}
|
|
40327
41094
|
}
|
|
40328
41095
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40378,6 +41145,13 @@ class PositionBitsEncoder {
|
|
|
40378
41145
|
encode({ sheetId, col, row }) {
|
|
40379
41146
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40380
41147
|
}
|
|
41148
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
41149
|
+
const positions = [];
|
|
41150
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
41151
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
41152
|
+
});
|
|
41153
|
+
return positions;
|
|
41154
|
+
}
|
|
40381
41155
|
decode(id) {
|
|
40382
41156
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40383
41157
|
const row = Number(id & 2097151n);
|
|
@@ -40385,6 +41159,10 @@ class PositionBitsEncoder {
|
|
|
40385
41159
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40386
41160
|
return { sheetId, col, row };
|
|
40387
41161
|
}
|
|
41162
|
+
decodeToBoundingBox(id) {
|
|
41163
|
+
const { sheetId, col, row } = this.decode(id);
|
|
41164
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
41165
|
+
}
|
|
40388
41166
|
encodeSheet(sheetId) {
|
|
40389
41167
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40390
41168
|
if (sheetKey === undefined) {
|
|
@@ -40555,7 +41333,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
40555
41333
|
// Getters
|
|
40556
41334
|
// ---------------------------------------------------------------------------
|
|
40557
41335
|
evaluateFormula(sheetId, formulaString) {
|
|
40558
|
-
|
|
41336
|
+
try {
|
|
41337
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41338
|
+
}
|
|
41339
|
+
catch (error) {
|
|
41340
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41341
|
+
}
|
|
40559
41342
|
}
|
|
40560
41343
|
/**
|
|
40561
41344
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -40824,7 +41607,7 @@ class CustomColorsPlugin extends UIPlugin {
|
|
|
40824
41607
|
}
|
|
40825
41608
|
|
|
40826
41609
|
class EvaluationChartPlugin extends UIPlugin {
|
|
40827
|
-
static getters = ["getChartRuntime", "
|
|
41610
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
40828
41611
|
charts = {};
|
|
40829
41612
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
40830
41613
|
handle(cmd) {
|
|
@@ -40862,25 +41645,26 @@ class EvaluationChartPlugin extends UIPlugin {
|
|
|
40862
41645
|
return this.charts[figureId];
|
|
40863
41646
|
}
|
|
40864
41647
|
/**
|
|
40865
|
-
* Get the background
|
|
40866
|
-
* of the chart. In order of priority, it will return :
|
|
40867
|
-
*
|
|
40868
|
-
* - the chart background color if one is defined
|
|
40869
|
-
* - the fill color of the cell if one is defined
|
|
40870
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
40871
|
-
* - the default chart color if no other color is defined
|
|
41648
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
40872
41649
|
*/
|
|
40873
|
-
|
|
41650
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
40874
41651
|
if (chartBackground)
|
|
40875
|
-
return chartBackground;
|
|
41652
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
40876
41653
|
if (!mainRange) {
|
|
40877
|
-
return
|
|
41654
|
+
return {
|
|
41655
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41656
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41657
|
+
};
|
|
40878
41658
|
}
|
|
40879
41659
|
const col = mainRange.zone.left;
|
|
40880
41660
|
const row = mainRange.zone.top;
|
|
40881
41661
|
const sheetId = mainRange.sheetId;
|
|
40882
41662
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
40883
|
-
|
|
41663
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41664
|
+
return {
|
|
41665
|
+
background,
|
|
41666
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41667
|
+
};
|
|
40884
41668
|
}
|
|
40885
41669
|
exportForExcel(data) {
|
|
40886
41670
|
for (const sheet of data.sheets) {
|
|
@@ -40991,45 +41775,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
40991
41775
|
getComputedStyles(sheetId) {
|
|
40992
41776
|
const computedStyle = {};
|
|
40993
41777
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
40994
|
-
|
|
40995
|
-
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
|
|
41005
|
-
for (let
|
|
41006
|
-
|
|
41007
|
-
|
|
41008
|
-
|
|
41009
|
-
const
|
|
41010
|
-
|
|
41011
|
-
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
});
|
|
41016
|
-
}
|
|
41017
|
-
return value;
|
|
41018
|
-
});
|
|
41019
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41020
|
-
if (!computedStyle[col])
|
|
41021
|
-
computedStyle[col] = [];
|
|
41022
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41023
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41778
|
+
switch (cf.rule.type) {
|
|
41779
|
+
case "ColorScaleRule":
|
|
41780
|
+
for (let range of cf.ranges) {
|
|
41781
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
41782
|
+
}
|
|
41783
|
+
break;
|
|
41784
|
+
case "CellIsRule":
|
|
41785
|
+
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
41786
|
+
for (let ref of cf.ranges) {
|
|
41787
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
41788
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41789
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
41790
|
+
const predicate = this.rulePredicate[cf.rule.type];
|
|
41791
|
+
const target = { sheetId, col, row };
|
|
41792
|
+
const values = cf.rule.values.map((value, i) => {
|
|
41793
|
+
const compiledFormula = formulas[i];
|
|
41794
|
+
if (compiledFormula) {
|
|
41795
|
+
return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
|
|
41796
|
+
...compiledFormula,
|
|
41797
|
+
dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
|
|
41798
|
+
});
|
|
41024
41799
|
}
|
|
41800
|
+
return value;
|
|
41801
|
+
});
|
|
41802
|
+
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41803
|
+
if (!computedStyle[col])
|
|
41804
|
+
computedStyle[col] = [];
|
|
41805
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41806
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41025
41807
|
}
|
|
41026
41808
|
}
|
|
41027
41809
|
}
|
|
41028
|
-
|
|
41029
|
-
|
|
41030
|
-
}
|
|
41031
|
-
catch (_) {
|
|
41032
|
-
// we don't care about the errors within the evaluation of a rule
|
|
41810
|
+
}
|
|
41811
|
+
break;
|
|
41033
41812
|
}
|
|
41034
41813
|
}
|
|
41035
41814
|
return computedStyle;
|
|
@@ -41801,7 +42580,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41801
42580
|
let row = zone.bottom;
|
|
41802
42581
|
if (col > 0) {
|
|
41803
42582
|
let leftPosition = { sheetId, col: col - 1, row };
|
|
41804
|
-
while (this.getters.
|
|
42583
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
41805
42584
|
this.getters.getCell(leftPosition)?.content) {
|
|
41806
42585
|
row += 1;
|
|
41807
42586
|
leftPosition = { sheetId, col: col - 1, row };
|
|
@@ -41811,7 +42590,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41811
42590
|
col = zone.right;
|
|
41812
42591
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
41813
42592
|
let rightPosition = { sheetId, col: col + 1, row };
|
|
41814
|
-
while (this.getters.
|
|
42593
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
41815
42594
|
this.getters.getCell(rightPosition)?.content) {
|
|
41816
42595
|
row += 1;
|
|
41817
42596
|
rightPosition = { sheetId, col: col + 1, row };
|
|
@@ -52192,7 +52971,7 @@ class Spreadsheet extends Component {
|
|
|
52192
52971
|
}
|
|
52193
52972
|
onKeydown(ev) {
|
|
52194
52973
|
let keyDownString = "";
|
|
52195
|
-
if (ev
|
|
52974
|
+
if (isCtrlKey(ev)) {
|
|
52196
52975
|
keyDownString += "CTRL+";
|
|
52197
52976
|
}
|
|
52198
52977
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55904,6 +56683,7 @@ const helpers = {
|
|
|
55904
56683
|
colorToRGBA,
|
|
55905
56684
|
positionToZone,
|
|
55906
56685
|
isDefined: isDefined$1,
|
|
56686
|
+
isMatrix,
|
|
55907
56687
|
lazy,
|
|
55908
56688
|
genericRepeat,
|
|
55909
56689
|
createAction,
|
|
@@ -55952,6 +56732,6 @@ const constants = {
|
|
|
55952
56732
|
export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
|
|
55953
56733
|
|
|
55954
56734
|
|
|
55955
|
-
__info__.version = "17.0.
|
|
55956
|
-
__info__.date = "
|
|
55957
|
-
__info__.hash = "
|
|
56735
|
+
__info__.version = "17.0.7";
|
|
56736
|
+
__info__.date = "2024-01-12T13:42:04.654Z";
|
|
56737
|
+
__info__.hash = "cadb817";
|