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