@odoo/o-spreadsheet 18.4.0-alpha.1 → 18.4.0-alpha.2
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 +1695 -879
- package/dist/o-spreadsheet.d.ts +245 -135
- package/dist/o-spreadsheet.esm.js +1695 -879
- package/dist/o-spreadsheet.iife.js +1695 -879
- package/dist/o-spreadsheet.iife.min.js +401 -398
- package/dist/o_spreadsheet.xml +77 -11
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 18.4.0-alpha.
|
|
6
|
-
* @date 2025-05-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.4.0-alpha.2
|
|
6
|
+
* @date 2025-05-12T05:28:06.109Z
|
|
7
|
+
* @hash a11158e
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -65,12 +65,25 @@ function createAction(item) {
|
|
|
65
65
|
class Registry {
|
|
66
66
|
content = {};
|
|
67
67
|
/**
|
|
68
|
-
* Add an item to the registry
|
|
68
|
+
* Add an item to the registry, you can only add if there is no item
|
|
69
|
+
* already present in the registery with the given key
|
|
69
70
|
*
|
|
70
71
|
* Note that this also returns the registry, so another add method call can
|
|
71
72
|
* be chained
|
|
72
73
|
*/
|
|
73
74
|
add(key, value) {
|
|
75
|
+
if (key in this.content) {
|
|
76
|
+
throw new Error(`${key} is already present in this registry!`);
|
|
77
|
+
}
|
|
78
|
+
return this.replace(key, value);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Replace (or add) an item to the registry
|
|
82
|
+
*
|
|
83
|
+
* Note that this also returns the registry, so another add method call can
|
|
84
|
+
* be chained
|
|
85
|
+
*/
|
|
86
|
+
replace(key, value) {
|
|
74
87
|
this.content[key] = value;
|
|
75
88
|
return this;
|
|
76
89
|
}
|
|
@@ -3548,6 +3561,7 @@ const readonlyAllowedCommands = new Set([
|
|
|
3548
3561
|
"EVALUATE_CHARTS",
|
|
3549
3562
|
"SET_FORMULA_VISIBILITY",
|
|
3550
3563
|
"UPDATE_FILTER",
|
|
3564
|
+
"UPDATE_CHART",
|
|
3551
3565
|
]);
|
|
3552
3566
|
const coreTypes = new Set([
|
|
3553
3567
|
/** CELLS */
|
|
@@ -3903,14 +3917,14 @@ class SplillBlockedError extends EvaluationError {
|
|
|
3903
3917
|
super(message, CellErrorType.SpilledBlocked);
|
|
3904
3918
|
}
|
|
3905
3919
|
}
|
|
3920
|
+
class DivisionByZeroError extends EvaluationError {
|
|
3921
|
+
constructor(message = _t("Division by zero")) {
|
|
3922
|
+
super(message, CellErrorType.DivisionByZero);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3906
3925
|
|
|
3907
3926
|
// HELPERS
|
|
3908
3927
|
const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"];
|
|
3909
|
-
function assert(condition, message, value) {
|
|
3910
|
-
if (!condition()) {
|
|
3911
|
-
throw new EvaluationError(message, value);
|
|
3912
|
-
}
|
|
3913
|
-
}
|
|
3914
3928
|
function inferFormat(data) {
|
|
3915
3929
|
if (data === undefined) {
|
|
3916
3930
|
return undefined;
|
|
@@ -3989,12 +4003,6 @@ function toInteger(value, locale) {
|
|
|
3989
4003
|
function strictToInteger(value, locale) {
|
|
3990
4004
|
return Math.trunc(strictToNumber(value, locale));
|
|
3991
4005
|
}
|
|
3992
|
-
function assertNumberGreaterThanOrEqualToOne(value) {
|
|
3993
|
-
assert(() => value >= 1, _t("The function [[FUNCTION_NAME]] expects a number value to be greater than or equal to 1, but receives %s.", value.toString()));
|
|
3994
|
-
}
|
|
3995
|
-
function assertNotZero(value) {
|
|
3996
|
-
assert(() => value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
|
|
3997
|
-
}
|
|
3998
4006
|
function toString(data) {
|
|
3999
4007
|
const value = toValue(data);
|
|
4000
4008
|
switch (typeof value) {
|
|
@@ -6533,6 +6541,25 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
|
|
|
6533
6541
|
})
|
|
6534
6542
|
.filter(isDefined);
|
|
6535
6543
|
}
|
|
6544
|
+
function getNextSheetName(existingNames, baseName = "Sheet") {
|
|
6545
|
+
let i = 1;
|
|
6546
|
+
let name = `${baseName}${i}`;
|
|
6547
|
+
while (existingNames.includes(name)) {
|
|
6548
|
+
name = `${baseName}${i}`;
|
|
6549
|
+
i++;
|
|
6550
|
+
}
|
|
6551
|
+
return name;
|
|
6552
|
+
}
|
|
6553
|
+
function getDuplicateSheetName(nameToDuplicate, existingNames) {
|
|
6554
|
+
let i = 1;
|
|
6555
|
+
const baseName = _t("Copy of %s", nameToDuplicate);
|
|
6556
|
+
let name = baseName.toString();
|
|
6557
|
+
while (existingNames.includes(name)) {
|
|
6558
|
+
name = `${baseName} (${i})`;
|
|
6559
|
+
i++;
|
|
6560
|
+
}
|
|
6561
|
+
return name;
|
|
6562
|
+
}
|
|
6536
6563
|
|
|
6537
6564
|
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
6538
6565
|
return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
|
|
@@ -7575,6 +7602,37 @@ function countUnique(args) {
|
|
|
7575
7602
|
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
7576
7603
|
}
|
|
7577
7604
|
|
|
7605
|
+
function assert(condition, message) {
|
|
7606
|
+
if (!condition) {
|
|
7607
|
+
throw new EvaluationError(message);
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
7610
|
+
function assertNotZero(value, message = _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.")) {
|
|
7611
|
+
if (value === 0) {
|
|
7612
|
+
throw new DivisionByZeroError(message);
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
function isSingleColOrRow(arg) {
|
|
7616
|
+
return arg.length === 1 || arg[0].length === 1;
|
|
7617
|
+
}
|
|
7618
|
+
function areSameDimensions(...args) {
|
|
7619
|
+
if (args.every(isMatrix)) {
|
|
7620
|
+
const cols = args[0].length;
|
|
7621
|
+
const rows = args[0][0].length;
|
|
7622
|
+
for (const arg of args) {
|
|
7623
|
+
if (arg.length !== cols || arg[0].length !== rows) {
|
|
7624
|
+
return false;
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
return true;
|
|
7628
|
+
}
|
|
7629
|
+
return !args.some((arg) => Array.isArray(arg) && (arg.length !== 1 || arg[0].length !== 1));
|
|
7630
|
+
}
|
|
7631
|
+
function isSquareMatrix(arg) {
|
|
7632
|
+
return arg.length === arg[0].length;
|
|
7633
|
+
}
|
|
7634
|
+
const expectNumberGreaterThanOrEqualToOne = (value) => _t("The function [[FUNCTION_NAME]] expects a number value to be greater than or equal to 1, but receives %s.", value);
|
|
7635
|
+
|
|
7578
7636
|
function getUnitMatrix(n) {
|
|
7579
7637
|
const matrix = Array(n);
|
|
7580
7638
|
for (let i = 0; i < n; i++) {
|
|
@@ -7703,7 +7761,7 @@ function toScalar(matrix) {
|
|
|
7703
7761
|
|
|
7704
7762
|
function assertSameNumberOfElements(...args) {
|
|
7705
7763
|
const dims = args[0].length;
|
|
7706
|
-
args.forEach((arg, i) => assert(
|
|
7764
|
+
args.forEach((arg, i) => assert(arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
|
|
7707
7765
|
}
|
|
7708
7766
|
function average(values, locale) {
|
|
7709
7767
|
let count = 0;
|
|
@@ -7867,7 +7925,7 @@ function fullLinearRegression(X, Y, computeIntercept = true, verbose = false) {
|
|
|
7867
7925
|
*/
|
|
7868
7926
|
function polynomialRegression(flatY, flatX, order, intercept) {
|
|
7869
7927
|
assertSameNumberOfElements(flatX, flatY);
|
|
7870
|
-
assert(
|
|
7928
|
+
assert(order >= 1, _t("Function [[FUNCTION_NAME]] A regression of order less than 1 cannot be possible."));
|
|
7871
7929
|
const yMatrix = [flatY];
|
|
7872
7930
|
const xMatrix = flatX.map((x) => range(0, order).map((i) => Math.pow(x, order - i)));
|
|
7873
7931
|
if (intercept) {
|
|
@@ -8275,6 +8333,24 @@ const monthNumberAdapter = {
|
|
|
8275
8333
|
return `${normalizedValue}`;
|
|
8276
8334
|
},
|
|
8277
8335
|
};
|
|
8336
|
+
/**
|
|
8337
|
+
* normalizes month number + year
|
|
8338
|
+
*/
|
|
8339
|
+
const monthAdapter = {
|
|
8340
|
+
normalizeFunctionValue(value) {
|
|
8341
|
+
const date = toNumber(value, DEFAULT_LOCALE);
|
|
8342
|
+
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
|
|
8343
|
+
},
|
|
8344
|
+
toValueAndFormat(normalizedValue) {
|
|
8345
|
+
return {
|
|
8346
|
+
value: toNumber(normalizedValue, DEFAULT_LOCALE),
|
|
8347
|
+
format: "mmmm yyyy",
|
|
8348
|
+
};
|
|
8349
|
+
},
|
|
8350
|
+
toFunctionValue(normalizedValue) {
|
|
8351
|
+
return `"${normalizedValue}"`;
|
|
8352
|
+
},
|
|
8353
|
+
};
|
|
8278
8354
|
/**
|
|
8279
8355
|
* normalizes quarter number
|
|
8280
8356
|
*/
|
|
@@ -8410,6 +8486,7 @@ pivotTimeAdapterRegistry
|
|
|
8410
8486
|
.add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
|
|
8411
8487
|
.add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
|
|
8412
8488
|
.add("month_number", nullHandlerDecorator(monthNumberAdapter))
|
|
8489
|
+
.add("month", nullHandlerDecorator(monthAdapter))
|
|
8413
8490
|
.add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
|
|
8414
8491
|
.add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
|
|
8415
8492
|
.add("hour_number", nullHandlerDecorator(hourNumberAdapter))
|
|
@@ -8590,10 +8667,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
|
|
|
8590
8667
|
return normalizer(groupValueString, dimension.granularity);
|
|
8591
8668
|
}
|
|
8592
8669
|
function normalizeDateTime(value, granularity) {
|
|
8593
|
-
|
|
8594
|
-
throw new Error("Missing granularity");
|
|
8595
|
-
}
|
|
8596
|
-
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
8670
|
+
return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
|
|
8597
8671
|
}
|
|
8598
8672
|
function toFunctionPivotValue(value, dimension) {
|
|
8599
8673
|
if (value === null) {
|
|
@@ -8605,10 +8679,7 @@ function toFunctionPivotValue(value, dimension) {
|
|
|
8605
8679
|
return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
|
|
8606
8680
|
}
|
|
8607
8681
|
function toFunctionValueDateTime(value, granularity) {
|
|
8608
|
-
|
|
8609
|
-
throw new Error("Missing granularity");
|
|
8610
|
-
}
|
|
8611
|
-
return pivotTimeAdapter(granularity).toFunctionValue(value);
|
|
8682
|
+
return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
|
|
8612
8683
|
}
|
|
8613
8684
|
const pivotNormalizationValueRegistry = new Registry();
|
|
8614
8685
|
pivotNormalizationValueRegistry
|
|
@@ -10082,20 +10153,24 @@ function getElementMargins(el) {
|
|
|
10082
10153
|
}
|
|
10083
10154
|
|
|
10084
10155
|
const chartJsExtensionRegistry = new Registry();
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10156
|
+
function areChartJSExtensionsLoaded() {
|
|
10157
|
+
return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
|
|
10158
|
+
}
|
|
10159
|
+
function registerChartJSExtensions() {
|
|
10160
|
+
if (!window.Chart || areChartJSExtensionsLoaded()) {
|
|
10161
|
+
return;
|
|
10162
|
+
}
|
|
10163
|
+
for (const registryItem of chartJsExtensionRegistry.getAll()) {
|
|
10164
|
+
registryItem.register(window.Chart);
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
function unregisterChartJsExtensions() {
|
|
10168
|
+
if (!window.Chart) {
|
|
10169
|
+
return;
|
|
10170
|
+
}
|
|
10171
|
+
for (const registryItem of chartJsExtensionRegistry.getAll()) {
|
|
10172
|
+
registryItem.unregister(window.Chart);
|
|
10097
10173
|
}
|
|
10098
|
-
return window.Chart;
|
|
10099
10174
|
}
|
|
10100
10175
|
|
|
10101
10176
|
function getFunnelChartController() {
|
|
@@ -11718,13 +11793,35 @@ css /* scss */ `
|
|
|
11718
11793
|
}
|
|
11719
11794
|
}
|
|
11720
11795
|
`;
|
|
11721
|
-
chartJsExtensionRegistry.add("chartShowValuesPlugin",
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
chartJsExtensionRegistry.add("
|
|
11726
|
-
|
|
11727
|
-
|
|
11796
|
+
chartJsExtensionRegistry.add("chartShowValuesPlugin", {
|
|
11797
|
+
register: (Chart) => Chart.register(chartShowValuesPlugin),
|
|
11798
|
+
unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
|
|
11799
|
+
});
|
|
11800
|
+
chartJsExtensionRegistry.add("waterfallLinesPlugin", {
|
|
11801
|
+
register: (Chart) => Chart.register(waterfallLinesPlugin),
|
|
11802
|
+
unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
|
|
11803
|
+
});
|
|
11804
|
+
chartJsExtensionRegistry.add("funnelController", {
|
|
11805
|
+
register: (Chart) => Chart.register(getFunnelChartController()),
|
|
11806
|
+
unregister: (Chart) => Chart.unregister(getFunnelChartController()),
|
|
11807
|
+
});
|
|
11808
|
+
chartJsExtensionRegistry.add("funnelElement", {
|
|
11809
|
+
register: (Chart) => Chart.register(getFunnelChartElement()),
|
|
11810
|
+
unregister: (Chart) => Chart.unregister(getFunnelChartElement()),
|
|
11811
|
+
});
|
|
11812
|
+
chartJsExtensionRegistry.add("funnelTooltipPositioner", {
|
|
11813
|
+
register: (Chart) => (Chart.Tooltip.positioners.funnelTooltipPositioner = funnelTooltipPositioner),
|
|
11814
|
+
// @ts-expect-error
|
|
11815
|
+
unregister: (Chart) => (Chart.Tooltip.positioners.funnelTooltipPositioner = undefined),
|
|
11816
|
+
});
|
|
11817
|
+
chartJsExtensionRegistry.add("sunburstLabelsPlugin", {
|
|
11818
|
+
register: (Chart) => Chart.register(sunburstLabelsPlugin),
|
|
11819
|
+
unregister: (Chart) => Chart.unregister(sunburstLabelsPlugin),
|
|
11820
|
+
});
|
|
11821
|
+
chartJsExtensionRegistry.add("sunburstHoverPlugin", {
|
|
11822
|
+
register: (Chart) => Chart.register(sunburstHoverPlugin),
|
|
11823
|
+
unregister: (Chart) => Chart.unregister(sunburstHoverPlugin),
|
|
11824
|
+
});
|
|
11728
11825
|
class ChartJsComponent extends owl.Component {
|
|
11729
11826
|
static template = "o-spreadsheet-ChartJsComponent";
|
|
11730
11827
|
static props = {
|
|
@@ -11776,8 +11873,7 @@ class ChartJsComponent extends owl.Component {
|
|
|
11776
11873
|
createChart(chartData) {
|
|
11777
11874
|
const canvas = this.canvas.el;
|
|
11778
11875
|
const ctx = canvas.getContext("2d");
|
|
11779
|
-
|
|
11780
|
-
this.chart = new Chart(ctx, chartData);
|
|
11876
|
+
this.chart = new window.Chart(ctx, chartData);
|
|
11781
11877
|
}
|
|
11782
11878
|
updateChartJs(chartData) {
|
|
11783
11879
|
if (chartData.data && chartData.data.datasets) {
|
|
@@ -12403,6 +12499,230 @@ class ScorecardChart extends owl.Component {
|
|
|
12403
12499
|
}
|
|
12404
12500
|
}
|
|
12405
12501
|
|
|
12502
|
+
const macRegex = /Mac/i;
|
|
12503
|
+
const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
12504
|
+
/**
|
|
12505
|
+
* Return true if the event was triggered from
|
|
12506
|
+
* a child element.
|
|
12507
|
+
*/
|
|
12508
|
+
function isChildEvent(parent, ev) {
|
|
12509
|
+
if (!parent)
|
|
12510
|
+
return false;
|
|
12511
|
+
return !!ev.target && parent.contains(ev.target);
|
|
12512
|
+
}
|
|
12513
|
+
function gridOverlayPosition() {
|
|
12514
|
+
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
12515
|
+
if (spreadsheetElement) {
|
|
12516
|
+
const { top, left } = spreadsheetElement.getBoundingClientRect();
|
|
12517
|
+
return { top, left };
|
|
12518
|
+
}
|
|
12519
|
+
throw new Error("Can't find spreadsheet position");
|
|
12520
|
+
}
|
|
12521
|
+
function getBoundingRectAsPOJO(el) {
|
|
12522
|
+
const rect = el.getBoundingClientRect();
|
|
12523
|
+
return {
|
|
12524
|
+
x: rect.x,
|
|
12525
|
+
y: rect.y,
|
|
12526
|
+
width: rect.width,
|
|
12527
|
+
height: rect.height,
|
|
12528
|
+
};
|
|
12529
|
+
}
|
|
12530
|
+
/**
|
|
12531
|
+
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
12532
|
+
*/
|
|
12533
|
+
function* iterateChildren(el) {
|
|
12534
|
+
yield el;
|
|
12535
|
+
if (el.hasChildNodes()) {
|
|
12536
|
+
for (const child of el.childNodes) {
|
|
12537
|
+
yield* iterateChildren(child);
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12540
|
+
}
|
|
12541
|
+
function getOpenedMenus() {
|
|
12542
|
+
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
12543
|
+
}
|
|
12544
|
+
function getCurrentSelection(el) {
|
|
12545
|
+
const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
|
|
12546
|
+
const startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
|
|
12547
|
+
const endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
|
|
12548
|
+
return {
|
|
12549
|
+
start: startSizeBefore,
|
|
12550
|
+
end: endSizeBefore,
|
|
12551
|
+
};
|
|
12552
|
+
}
|
|
12553
|
+
function getStartAndEndSelection(el) {
|
|
12554
|
+
const selection = document.getSelection();
|
|
12555
|
+
return {
|
|
12556
|
+
startElement: selection.anchorNode || el,
|
|
12557
|
+
startSelectionOffset: selection.anchorOffset,
|
|
12558
|
+
endElement: selection.focusNode || el,
|
|
12559
|
+
endSelectionOffset: selection.focusOffset,
|
|
12560
|
+
};
|
|
12561
|
+
}
|
|
12562
|
+
/**
|
|
12563
|
+
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
12564
|
+
* The selected node is either a Text node or an Element node.
|
|
12565
|
+
*
|
|
12566
|
+
* case 1 -Text node:
|
|
12567
|
+
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
12568
|
+
* content length of all previous nodes.
|
|
12569
|
+
*
|
|
12570
|
+
* case 2 - Element node:
|
|
12571
|
+
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
12572
|
+
* all the nodes prior to the selected node as well as the content of the child node before the offset.
|
|
12573
|
+
*
|
|
12574
|
+
* See the MDN documentation for more details.
|
|
12575
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
12576
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
12577
|
+
*
|
|
12578
|
+
*/
|
|
12579
|
+
function findSelectionIndex(el, nodeToFind, nodeOffset) {
|
|
12580
|
+
let usedCharacters = 0;
|
|
12581
|
+
const it = iterateChildren(el);
|
|
12582
|
+
let current = it.next();
|
|
12583
|
+
let isFirstParagraph = true;
|
|
12584
|
+
while (!current.done && current.value !== nodeToFind) {
|
|
12585
|
+
if (!current.value.hasChildNodes()) {
|
|
12586
|
+
if (current.value.textContent) {
|
|
12587
|
+
usedCharacters += current.value.textContent.length;
|
|
12588
|
+
}
|
|
12589
|
+
}
|
|
12590
|
+
// One new paragraph = one new line character, except for the first paragraph
|
|
12591
|
+
if (current.value.nodeName === "P" ||
|
|
12592
|
+
(current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
|
|
12593
|
+
) {
|
|
12594
|
+
if (isFirstParagraph) {
|
|
12595
|
+
isFirstParagraph = false;
|
|
12596
|
+
}
|
|
12597
|
+
else {
|
|
12598
|
+
usedCharacters++;
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
current = it.next();
|
|
12602
|
+
}
|
|
12603
|
+
if (current.value !== nodeToFind) {
|
|
12604
|
+
/** This situation can happen if the code is called while the selection is not currently on the element.
|
|
12605
|
+
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
12606
|
+
*
|
|
12607
|
+
* A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
12608
|
+
*/
|
|
12609
|
+
return 0;
|
|
12610
|
+
}
|
|
12611
|
+
else {
|
|
12612
|
+
if (!current.value.hasChildNodes()) {
|
|
12613
|
+
usedCharacters += nodeOffset;
|
|
12614
|
+
}
|
|
12615
|
+
else {
|
|
12616
|
+
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
12617
|
+
usedCharacters += children.reduce((acc, child, index) => {
|
|
12618
|
+
if (child.textContent !== null) {
|
|
12619
|
+
// need to account for paragraph nodes that implicitly add a new line
|
|
12620
|
+
// except for the last paragraph
|
|
12621
|
+
let chars = child.textContent.length;
|
|
12622
|
+
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
12623
|
+
chars++;
|
|
12624
|
+
}
|
|
12625
|
+
return acc + chars;
|
|
12626
|
+
}
|
|
12627
|
+
else {
|
|
12628
|
+
return acc;
|
|
12629
|
+
}
|
|
12630
|
+
}, 0);
|
|
12631
|
+
}
|
|
12632
|
+
}
|
|
12633
|
+
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
12634
|
+
usedCharacters++;
|
|
12635
|
+
}
|
|
12636
|
+
return usedCharacters;
|
|
12637
|
+
}
|
|
12638
|
+
const letterRegex = /^[a-zA-Z]$/;
|
|
12639
|
+
/**
|
|
12640
|
+
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
12641
|
+
*
|
|
12642
|
+
* @argument ev - The keyboard event to transform
|
|
12643
|
+
* @argument mode - Use either ev.key of ev.code to get the string shortcut
|
|
12644
|
+
*
|
|
12645
|
+
* @example
|
|
12646
|
+
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
12647
|
+
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
12648
|
+
*/
|
|
12649
|
+
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
12650
|
+
let keyDownString = "";
|
|
12651
|
+
if (!MODIFIER_KEYS.includes(ev.key)) {
|
|
12652
|
+
if (isCtrlKey(ev))
|
|
12653
|
+
keyDownString += "Ctrl+";
|
|
12654
|
+
if (ev.altKey)
|
|
12655
|
+
keyDownString += "Alt+";
|
|
12656
|
+
if (ev.shiftKey)
|
|
12657
|
+
keyDownString += "Shift+";
|
|
12658
|
+
}
|
|
12659
|
+
const key = mode === "key" ? ev.key : ev.code;
|
|
12660
|
+
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
12661
|
+
return keyDownString;
|
|
12662
|
+
}
|
|
12663
|
+
function isMacOS() {
|
|
12664
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
12665
|
+
}
|
|
12666
|
+
/**
|
|
12667
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
12668
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
12669
|
+
* On Mac, this is the "meta" or "command" key.
|
|
12670
|
+
*/
|
|
12671
|
+
function isCtrlKey(ev) {
|
|
12672
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
12673
|
+
}
|
|
12674
|
+
/**
|
|
12675
|
+
* @param {MouseEvent} ev - The mouse event.
|
|
12676
|
+
* @returns {boolean} Returns true if the event was triggered by a middle-click
|
|
12677
|
+
* or a Ctrl + Click (Cmd + Click on Mac).
|
|
12678
|
+
*/
|
|
12679
|
+
function isMiddleClickOrCtrlClick(ev) {
|
|
12680
|
+
return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
|
|
12681
|
+
}
|
|
12682
|
+
async function convertImageToPng(imageUrl) {
|
|
12683
|
+
return new Promise((resolve, reject) => {
|
|
12684
|
+
const image = new Image();
|
|
12685
|
+
image.addEventListener("load", () => {
|
|
12686
|
+
const canvas = document.createElement("canvas");
|
|
12687
|
+
canvas.width = image.width;
|
|
12688
|
+
canvas.height = image.height;
|
|
12689
|
+
const ctx = canvas.getContext("2d");
|
|
12690
|
+
ctx?.drawImage(image, 0, 0);
|
|
12691
|
+
canvas.toBlob(resolve, "image/png");
|
|
12692
|
+
});
|
|
12693
|
+
image.addEventListener("error", reject);
|
|
12694
|
+
image.src = imageUrl;
|
|
12695
|
+
});
|
|
12696
|
+
}
|
|
12697
|
+
function downloadFile(dataUrl, fileName) {
|
|
12698
|
+
const a = document.createElement("a");
|
|
12699
|
+
a.href = dataUrl;
|
|
12700
|
+
a.download = fileName;
|
|
12701
|
+
document.body.appendChild(a);
|
|
12702
|
+
a.click();
|
|
12703
|
+
document.body.removeChild(a);
|
|
12704
|
+
}
|
|
12705
|
+
/**
|
|
12706
|
+
* Detects if the current browser is Firefox
|
|
12707
|
+
*/
|
|
12708
|
+
function isBrowserFirefox() {
|
|
12709
|
+
return /Firefox/i.test(navigator.userAgent);
|
|
12710
|
+
}
|
|
12711
|
+
|
|
12712
|
+
/**
|
|
12713
|
+
* Convert a JS color hexadecimal to an excel compatible color.
|
|
12714
|
+
*
|
|
12715
|
+
* In Excel the color don't start with a '#' and the format is AARRGGBB instead of RRGGBBAA
|
|
12716
|
+
*/
|
|
12717
|
+
function toXlsxHexColor(color) {
|
|
12718
|
+
color = toHex(color).replace("#", "");
|
|
12719
|
+
// alpha channel goes first
|
|
12720
|
+
if (color.length === 8) {
|
|
12721
|
+
return color.slice(6) + color.slice(0, 6);
|
|
12722
|
+
}
|
|
12723
|
+
return color;
|
|
12724
|
+
}
|
|
12725
|
+
|
|
12406
12726
|
const GAUGE_PADDING_SIDE = 30;
|
|
12407
12727
|
const GAUGE_PADDING_TOP = 10;
|
|
12408
12728
|
const GAUGE_PADDING_BOTTOM = 20;
|
|
@@ -12742,38 +13062,6 @@ function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY
|
|
|
12742
13062
|
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
12743
13063
|
}
|
|
12744
13064
|
|
|
12745
|
-
class GaugeChartComponent extends owl.Component {
|
|
12746
|
-
static template = "o-spreadsheet-GaugeChartComponent";
|
|
12747
|
-
canvas = owl.useRef("chartContainer");
|
|
12748
|
-
get runtime() {
|
|
12749
|
-
return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
|
|
12750
|
-
}
|
|
12751
|
-
setup() {
|
|
12752
|
-
owl.useEffect(() => drawGaugeChart(this.canvas.el, this.runtime), () => {
|
|
12753
|
-
const canvas = this.canvas.el;
|
|
12754
|
-
const rect = canvas.getBoundingClientRect();
|
|
12755
|
-
return [rect.width, rect.height, this.runtime, this.canvas.el, window.devicePixelRatio];
|
|
12756
|
-
});
|
|
12757
|
-
}
|
|
12758
|
-
}
|
|
12759
|
-
GaugeChartComponent.props = {
|
|
12760
|
-
figureUI: Object,
|
|
12761
|
-
};
|
|
12762
|
-
|
|
12763
|
-
/**
|
|
12764
|
-
* Convert a JS color hexadecimal to an excel compatible color.
|
|
12765
|
-
*
|
|
12766
|
-
* In Excel the color don't start with a '#' and the format is AARRGGBB instead of RRGGBBAA
|
|
12767
|
-
*/
|
|
12768
|
-
function toXlsxHexColor(color) {
|
|
12769
|
-
color = toHex(color).replace("#", "");
|
|
12770
|
-
// alpha channel goes first
|
|
12771
|
-
if (color.length === 8) {
|
|
12772
|
-
return color.slice(6) + color.slice(0, 6);
|
|
12773
|
-
}
|
|
12774
|
-
return color;
|
|
12775
|
-
}
|
|
12776
|
-
|
|
12777
13065
|
const CHART_COMMON_OPTIONS = {
|
|
12778
13066
|
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
12779
13067
|
responsive: true, // will resize when its container is resized
|
|
@@ -12787,6 +13075,7 @@ const CHART_COMMON_OPTIONS = {
|
|
|
12787
13075
|
},
|
|
12788
13076
|
},
|
|
12789
13077
|
animation: false,
|
|
13078
|
+
events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
|
|
12790
13079
|
};
|
|
12791
13080
|
function chartToImageUrl(runtime, figure, type) {
|
|
12792
13081
|
// wrap the canvas in a div with a fixed size because chart.js would
|
|
@@ -12836,8 +13125,7 @@ async function chartToImageFile(runtime, figure, type) {
|
|
|
12836
13125
|
if ("chartJsConfig" in runtime) {
|
|
12837
13126
|
const config = deepCopy(runtime.chartJsConfig);
|
|
12838
13127
|
config.plugins = [backgroundColorChartJSPlugin];
|
|
12839
|
-
const
|
|
12840
|
-
const chart = new Chart(canvas, config);
|
|
13128
|
+
const chart = new window.Chart(canvas, config);
|
|
12841
13129
|
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
12842
13130
|
chart.destroy();
|
|
12843
13131
|
}
|
|
@@ -13238,10 +13526,11 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
|
|
|
13238
13526
|
axisFormats,
|
|
13239
13527
|
labels,
|
|
13240
13528
|
locale: getters.getLocale(),
|
|
13529
|
+
topPadding: getTopPaddingForDashboard(definition, getters),
|
|
13241
13530
|
};
|
|
13242
13531
|
}
|
|
13243
13532
|
function getPyramidChartData(definition, dataSets, labelRange, getters) {
|
|
13244
|
-
const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
|
|
13533
|
+
const barChartData = getBarChartData(definition, dataSets.slice(0, 2), labelRange, getters);
|
|
13245
13534
|
const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
|
|
13246
13535
|
const pyramidDatasetValues = [];
|
|
13247
13536
|
if (barDataset[0]) {
|
|
@@ -13297,6 +13586,7 @@ function getLineChartData(definition, dataSets, labelRange, getters) {
|
|
|
13297
13586
|
locale: getters.getLocale(),
|
|
13298
13587
|
trendDataSetsValues,
|
|
13299
13588
|
axisType,
|
|
13589
|
+
topPadding: getTopPaddingForDashboard(definition, getters),
|
|
13300
13590
|
};
|
|
13301
13591
|
}
|
|
13302
13592
|
function getPieChartData(definition, dataSets, labelRange, getters) {
|
|
@@ -13317,6 +13607,7 @@ function getPieChartData(definition, dataSets, labelRange, getters) {
|
|
|
13317
13607
|
axisFormats: { y: dataSetFormat },
|
|
13318
13608
|
labels,
|
|
13319
13609
|
locale: getters.getLocale(),
|
|
13610
|
+
topPadding: getTopPaddingForDashboard(definition, getters),
|
|
13320
13611
|
};
|
|
13321
13612
|
}
|
|
13322
13613
|
function getRadarChartData(definition, dataSets, labelRange, getters) {
|
|
@@ -13340,7 +13631,8 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
|
|
|
13340
13631
|
locale: getters.getLocale(),
|
|
13341
13632
|
};
|
|
13342
13633
|
}
|
|
13343
|
-
function getGeoChartData(definition,
|
|
13634
|
+
function getGeoChartData(definition, fullDataSets, labelRange, getters) {
|
|
13635
|
+
const dataSets = fullDataSets.slice(0, 1);
|
|
13344
13636
|
const labelValues = getChartLabelValues(getters, dataSets, labelRange);
|
|
13345
13637
|
let labels = labelValues.formattedValues;
|
|
13346
13638
|
if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
|
|
@@ -13589,11 +13881,10 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
|
|
|
13589
13881
|
}
|
|
13590
13882
|
let missingTimeAdapterAlreadyWarned = false;
|
|
13591
13883
|
function isLuxonTimeAdapterInstalled() {
|
|
13592
|
-
|
|
13593
|
-
if (!Chart) {
|
|
13884
|
+
if (!window.Chart) {
|
|
13594
13885
|
return false;
|
|
13595
13886
|
}
|
|
13596
|
-
const adapter = new Chart._adapters._date({});
|
|
13887
|
+
const adapter = new window.Chart._adapters._date({});
|
|
13597
13888
|
// @ts-ignore
|
|
13598
13889
|
const isInstalled = adapter._id === "luxon";
|
|
13599
13890
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
@@ -13896,13 +14187,18 @@ function makeDatasetsCumulative(datasets, order) {
|
|
|
13896
14187
|
return { ...dataset, data };
|
|
13897
14188
|
});
|
|
13898
14189
|
}
|
|
14190
|
+
function getTopPaddingForDashboard(definition, getters) {
|
|
14191
|
+
const { title, legendPosition } = definition;
|
|
14192
|
+
const hasTitleOrLegendTop = (title && title.text) || legendPosition === "top";
|
|
14193
|
+
return getters.isDashboard() && !hasTitleOrLegendTop ? 30 : 0;
|
|
14194
|
+
}
|
|
13899
14195
|
|
|
13900
|
-
function getChartLayout(definition) {
|
|
14196
|
+
function getChartLayout(definition, args) {
|
|
13901
14197
|
return {
|
|
13902
14198
|
padding: {
|
|
13903
14199
|
left: CHART_PADDING$1,
|
|
13904
14200
|
right: CHART_PADDING$1,
|
|
13905
|
-
top: CHART_PADDING_TOP,
|
|
14201
|
+
top: Math.max(CHART_PADDING_TOP, args.topPadding || 0),
|
|
13906
14202
|
bottom: CHART_PADDING_BOTTOM,
|
|
13907
14203
|
},
|
|
13908
14204
|
};
|
|
@@ -14091,6 +14387,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
|
|
|
14091
14387
|
target.style.cursor = "default";
|
|
14092
14388
|
},
|
|
14093
14389
|
onClick: (event, legendItem, legend) => {
|
|
14390
|
+
if (event.type !== "click") {
|
|
14391
|
+
return;
|
|
14392
|
+
}
|
|
14094
14393
|
const index = legendItem.datasetIndex;
|
|
14095
14394
|
if (!legend.legendItems || index === undefined) {
|
|
14096
14395
|
return;
|
|
@@ -14919,6 +15218,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
|
|
|
14919
15218
|
getSunburstChartLegend: getSunburstChartLegend,
|
|
14920
15219
|
getSunburstChartTooltip: getSunburstChartTooltip,
|
|
14921
15220
|
getSunburstShowValues: getSunburstShowValues,
|
|
15221
|
+
getTopPaddingForDashboard: getTopPaddingForDashboard,
|
|
14922
15222
|
getTreeMapChartDatasets: getTreeMapChartDatasets,
|
|
14923
15223
|
getTreeMapChartTooltip: getTreeMapChartTooltip,
|
|
14924
15224
|
getTrendDatasetForBarChart: getTrendDatasetForBarChart,
|
|
@@ -15068,7 +15368,7 @@ function createBarChartRuntime(chart, getters) {
|
|
|
15068
15368
|
options: {
|
|
15069
15369
|
...CHART_COMMON_OPTIONS,
|
|
15070
15370
|
indexAxis: chart.horizontal ? "y" : "x",
|
|
15071
|
-
layout: getChartLayout(),
|
|
15371
|
+
layout: getChartLayout(definition, chartData),
|
|
15072
15372
|
scales: getBarChartScales(definition, chartData),
|
|
15073
15373
|
plugins: {
|
|
15074
15374
|
title: getChartTitle(definition),
|
|
@@ -15081,6 +15381,24 @@ function createBarChartRuntime(chart, getters) {
|
|
|
15081
15381
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
15082
15382
|
}
|
|
15083
15383
|
|
|
15384
|
+
class GaugeChartComponent extends owl.Component {
|
|
15385
|
+
static template = "o-spreadsheet-GaugeChartComponent";
|
|
15386
|
+
canvas = owl.useRef("chartContainer");
|
|
15387
|
+
get runtime() {
|
|
15388
|
+
return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
|
|
15389
|
+
}
|
|
15390
|
+
setup() {
|
|
15391
|
+
owl.useEffect(() => drawGaugeChart(this.canvas.el, this.runtime), () => {
|
|
15392
|
+
const canvas = this.canvas.el;
|
|
15393
|
+
const rect = canvas.getBoundingClientRect();
|
|
15394
|
+
return [rect.width, rect.height, this.runtime, this.canvas.el, window.devicePixelRatio];
|
|
15395
|
+
});
|
|
15396
|
+
}
|
|
15397
|
+
}
|
|
15398
|
+
GaugeChartComponent.props = {
|
|
15399
|
+
figureUI: Object,
|
|
15400
|
+
};
|
|
15401
|
+
|
|
15084
15402
|
class ComboChart extends AbstractChart {
|
|
15085
15403
|
dataSets;
|
|
15086
15404
|
labelRange;
|
|
@@ -15220,7 +15538,7 @@ function createComboChartRuntime(chart, getters) {
|
|
|
15220
15538
|
},
|
|
15221
15539
|
options: {
|
|
15222
15540
|
...CHART_COMMON_OPTIONS,
|
|
15223
|
-
layout: getChartLayout(),
|
|
15541
|
+
layout: getChartLayout(definition, chartData),
|
|
15224
15542
|
scales: getBarChartScales(definition, chartData),
|
|
15225
15543
|
plugins: {
|
|
15226
15544
|
title: getChartTitle(definition),
|
|
@@ -15364,7 +15682,7 @@ function createFunnelChartRuntime(chart, getters) {
|
|
|
15364
15682
|
options: {
|
|
15365
15683
|
...CHART_COMMON_OPTIONS,
|
|
15366
15684
|
indexAxis: "y",
|
|
15367
|
-
layout: getChartLayout(),
|
|
15685
|
+
layout: getChartLayout(definition, chartData),
|
|
15368
15686
|
scales: getFunnelChartScales(definition, chartData),
|
|
15369
15687
|
plugins: {
|
|
15370
15688
|
title: getChartTitle(definition),
|
|
@@ -15768,7 +16086,7 @@ function createGeoChartRuntime(chart, getters) {
|
|
|
15768
16086
|
},
|
|
15769
16087
|
options: {
|
|
15770
16088
|
...CHART_COMMON_OPTIONS,
|
|
15771
|
-
layout: getChartLayout(),
|
|
16089
|
+
layout: getChartLayout(definition, chartData),
|
|
15772
16090
|
scales: getGeoChartScales(definition, chartData),
|
|
15773
16091
|
plugins: {
|
|
15774
16092
|
title: getChartTitle(definition),
|
|
@@ -15926,7 +16244,7 @@ function createLineChartRuntime(chart, getters) {
|
|
|
15926
16244
|
},
|
|
15927
16245
|
options: {
|
|
15928
16246
|
...CHART_COMMON_OPTIONS,
|
|
15929
|
-
layout: getChartLayout(),
|
|
16247
|
+
layout: getChartLayout(definition, chartData),
|
|
15930
16248
|
scales: getLineChartScales(definition, chartData),
|
|
15931
16249
|
plugins: {
|
|
15932
16250
|
title: getChartTitle(definition),
|
|
@@ -15952,6 +16270,7 @@ class PieChart extends AbstractChart {
|
|
|
15952
16270
|
dataSetsHaveTitle;
|
|
15953
16271
|
isDoughnut;
|
|
15954
16272
|
showValues;
|
|
16273
|
+
pieHolePercentage;
|
|
15955
16274
|
constructor(definition, sheetId, getters) {
|
|
15956
16275
|
super(definition, sheetId, getters);
|
|
15957
16276
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -15962,6 +16281,7 @@ class PieChart extends AbstractChart {
|
|
|
15962
16281
|
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
15963
16282
|
this.isDoughnut = definition.isDoughnut;
|
|
15964
16283
|
this.showValues = definition.showValues;
|
|
16284
|
+
this.pieHolePercentage = definition.pieHolePercentage;
|
|
15965
16285
|
}
|
|
15966
16286
|
static transformDefinition(definition, executed) {
|
|
15967
16287
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -16013,6 +16333,7 @@ class PieChart extends AbstractChart {
|
|
|
16013
16333
|
aggregated: this.aggregated,
|
|
16014
16334
|
isDoughnut: this.isDoughnut,
|
|
16015
16335
|
showValues: this.showValues,
|
|
16336
|
+
pieHolePercentage: this.pieHolePercentage,
|
|
16016
16337
|
};
|
|
16017
16338
|
}
|
|
16018
16339
|
duplicateInDuplicatedSheet(newSheetId) {
|
|
@@ -16058,7 +16379,10 @@ function createPieChartRuntime(chart, getters) {
|
|
|
16058
16379
|
},
|
|
16059
16380
|
options: {
|
|
16060
16381
|
...CHART_COMMON_OPTIONS,
|
|
16061
|
-
|
|
16382
|
+
cutout: chart.isDoughnut && definition.pieHolePercentage !== undefined
|
|
16383
|
+
? definition.pieHolePercentage + "%"
|
|
16384
|
+
: undefined,
|
|
16385
|
+
layout: getChartLayout(definition, chartData),
|
|
16062
16386
|
plugins: {
|
|
16063
16387
|
title: getChartTitle(definition),
|
|
16064
16388
|
legend: getPieChartLegend(definition, chartData),
|
|
@@ -16085,7 +16409,7 @@ class PyramidChart extends AbstractChart {
|
|
|
16085
16409
|
showValues;
|
|
16086
16410
|
constructor(definition, sheetId, getters) {
|
|
16087
16411
|
super(definition, sheetId, getters);
|
|
16088
|
-
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle)
|
|
16412
|
+
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
16089
16413
|
this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
|
|
16090
16414
|
this.background = definition.background;
|
|
16091
16415
|
this.legendPosition = definition.legendPosition;
|
|
@@ -16195,7 +16519,7 @@ function createPyramidChartRuntime(chart, getters) {
|
|
|
16195
16519
|
options: {
|
|
16196
16520
|
...CHART_COMMON_OPTIONS,
|
|
16197
16521
|
indexAxis: "y",
|
|
16198
|
-
layout: getChartLayout(),
|
|
16522
|
+
layout: getChartLayout(definition, chartData),
|
|
16199
16523
|
scales: getPyramidChartScales(definition, chartData),
|
|
16200
16524
|
plugins: {
|
|
16201
16525
|
title: getChartTitle(definition),
|
|
@@ -16344,7 +16668,7 @@ function createRadarChartRuntime(chart, getters) {
|
|
|
16344
16668
|
},
|
|
16345
16669
|
options: {
|
|
16346
16670
|
...CHART_COMMON_OPTIONS,
|
|
16347
|
-
layout: getChartLayout(),
|
|
16671
|
+
layout: getChartLayout(definition, chartData),
|
|
16348
16672
|
scales: getRadarChartScales(definition, chartData),
|
|
16349
16673
|
plugins: {
|
|
16350
16674
|
title: getChartTitle(definition),
|
|
@@ -16497,7 +16821,7 @@ function createScatterChartRuntime(chart, getters) {
|
|
|
16497
16821
|
},
|
|
16498
16822
|
options: {
|
|
16499
16823
|
...CHART_COMMON_OPTIONS,
|
|
16500
|
-
layout: getChartLayout(),
|
|
16824
|
+
layout: getChartLayout(definition, chartData),
|
|
16501
16825
|
scales: getScatterChartScales(definition, chartData),
|
|
16502
16826
|
plugins: {
|
|
16503
16827
|
title: getChartTitle(definition),
|
|
@@ -16524,6 +16848,7 @@ class SunburstChart extends AbstractChart {
|
|
|
16524
16848
|
showLabels;
|
|
16525
16849
|
valuesDesign;
|
|
16526
16850
|
groupColors;
|
|
16851
|
+
pieHolePercentage;
|
|
16527
16852
|
constructor(definition, sheetId, getters) {
|
|
16528
16853
|
super(definition, sheetId, getters);
|
|
16529
16854
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -16535,6 +16860,7 @@ class SunburstChart extends AbstractChart {
|
|
|
16535
16860
|
this.showLabels = definition.showLabels;
|
|
16536
16861
|
this.valuesDesign = definition.valuesDesign;
|
|
16537
16862
|
this.groupColors = definition.groupColors;
|
|
16863
|
+
this.pieHolePercentage = definition.pieHolePercentage;
|
|
16538
16864
|
}
|
|
16539
16865
|
static transformDefinition(definition, executed) {
|
|
16540
16866
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -16597,6 +16923,7 @@ class SunburstChart extends AbstractChart {
|
|
|
16597
16923
|
showLabels: this.showLabels,
|
|
16598
16924
|
valuesDesign: this.valuesDesign,
|
|
16599
16925
|
groupColors: this.groupColors,
|
|
16926
|
+
pieHolePercentage: this.pieHolePercentage,
|
|
16600
16927
|
};
|
|
16601
16928
|
}
|
|
16602
16929
|
duplicateInDuplicatedSheet(newSheetId) {
|
|
@@ -16630,9 +16957,9 @@ function createSunburstChartRuntime(chart, getters) {
|
|
|
16630
16957
|
datasets: getSunburstChartDatasets(definition, chartData),
|
|
16631
16958
|
},
|
|
16632
16959
|
options: {
|
|
16633
|
-
cutout: "25%"
|
|
16960
|
+
cutout: chart.pieHolePercentage === undefined ? "25%" : `${chart.pieHolePercentage}%`,
|
|
16634
16961
|
...CHART_COMMON_OPTIONS,
|
|
16635
|
-
layout: getChartLayout(),
|
|
16962
|
+
layout: getChartLayout(definition, chartData),
|
|
16636
16963
|
plugins: {
|
|
16637
16964
|
title: getChartTitle(definition),
|
|
16638
16965
|
legend: getSunburstChartLegend(definition),
|
|
@@ -16780,7 +17107,7 @@ function createTreeMapChartRuntime(chart, getters) {
|
|
|
16780
17107
|
},
|
|
16781
17108
|
options: {
|
|
16782
17109
|
...CHART_COMMON_OPTIONS,
|
|
16783
|
-
layout: getChartLayout(),
|
|
17110
|
+
layout: getChartLayout(definition, chartData),
|
|
16784
17111
|
plugins: {
|
|
16785
17112
|
title: getChartTitle(definition),
|
|
16786
17113
|
legend: { display: false },
|
|
@@ -16936,7 +17263,7 @@ function createWaterfallChartRuntime(chart, getters) {
|
|
|
16936
17263
|
},
|
|
16937
17264
|
options: {
|
|
16938
17265
|
...CHART_COMMON_OPTIONS,
|
|
16939
|
-
layout: getChartLayout(),
|
|
17266
|
+
layout: getChartLayout(definition, chartData),
|
|
16940
17267
|
scales: getWaterfallChartScales(definition, chartData),
|
|
16941
17268
|
plugins: {
|
|
16942
17269
|
title: getChartTitle(definition),
|
|
@@ -17035,6 +17362,7 @@ chartRegistry.add("pyramid", {
|
|
|
17035
17362
|
transformDefinition: PyramidChart.transformDefinition,
|
|
17036
17363
|
getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
|
|
17037
17364
|
sequence: 80,
|
|
17365
|
+
dataSeriesLimit: 2,
|
|
17038
17366
|
});
|
|
17039
17367
|
chartRegistry.add("radar", {
|
|
17040
17368
|
match: (type) => type === "radar",
|
|
@@ -17053,6 +17381,7 @@ chartRegistry.add("geo", {
|
|
|
17053
17381
|
transformDefinition: GeoChart.transformDefinition,
|
|
17054
17382
|
getChartDefinitionFromContextCreation: GeoChart.getDefinitionFromContextCreation,
|
|
17055
17383
|
sequence: 90,
|
|
17384
|
+
dataSeriesLimit: 1,
|
|
17056
17385
|
});
|
|
17057
17386
|
chartRegistry.add("funnel", {
|
|
17058
17387
|
match: (type) => type === "funnel",
|
|
@@ -17062,6 +17391,7 @@ chartRegistry.add("funnel", {
|
|
|
17062
17391
|
transformDefinition: FunnelChart.transformDefinition,
|
|
17063
17392
|
getChartDefinitionFromContextCreation: FunnelChart.getDefinitionFromContextCreation,
|
|
17064
17393
|
sequence: 100,
|
|
17394
|
+
dataSeriesLimit: 1,
|
|
17065
17395
|
});
|
|
17066
17396
|
chartRegistry.add("sunburst", {
|
|
17067
17397
|
match: (type) => type === "sunburst",
|
|
@@ -17288,268 +17618,6 @@ chartSubtypeRegistry
|
|
|
17288
17618
|
preview: "o-spreadsheet-ChartPreview.TREE_MAP_CHART",
|
|
17289
17619
|
});
|
|
17290
17620
|
|
|
17291
|
-
// -----------------------------------------------------------------------------
|
|
17292
|
-
// STYLE
|
|
17293
|
-
// -----------------------------------------------------------------------------
|
|
17294
|
-
css /* scss */ `
|
|
17295
|
-
.o-chart-container {
|
|
17296
|
-
width: 100%;
|
|
17297
|
-
height: 100%;
|
|
17298
|
-
position: relative;
|
|
17299
|
-
}
|
|
17300
|
-
`;
|
|
17301
|
-
class ChartFigure extends owl.Component {
|
|
17302
|
-
static template = "o-spreadsheet-ChartFigure";
|
|
17303
|
-
static props = {
|
|
17304
|
-
figureUI: Object,
|
|
17305
|
-
onFigureDeleted: Function,
|
|
17306
|
-
};
|
|
17307
|
-
static components = {};
|
|
17308
|
-
onDoubleClick() {
|
|
17309
|
-
this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureUI.id });
|
|
17310
|
-
this.env.openSidePanel("ChartPanel");
|
|
17311
|
-
}
|
|
17312
|
-
get chartType() {
|
|
17313
|
-
return this.env.model.getters.getChartType(this.props.figureUI.id);
|
|
17314
|
-
}
|
|
17315
|
-
get chartComponent() {
|
|
17316
|
-
const type = this.chartType;
|
|
17317
|
-
const component = chartComponentRegistry.get(type);
|
|
17318
|
-
if (!component) {
|
|
17319
|
-
throw new Error(`Component is not defined for type ${type}`);
|
|
17320
|
-
}
|
|
17321
|
-
return component;
|
|
17322
|
-
}
|
|
17323
|
-
}
|
|
17324
|
-
|
|
17325
|
-
class ImageFigure extends owl.Component {
|
|
17326
|
-
static template = "o-spreadsheet-ImageFigure";
|
|
17327
|
-
static props = {
|
|
17328
|
-
figureUI: Object,
|
|
17329
|
-
onFigureDeleted: Function,
|
|
17330
|
-
};
|
|
17331
|
-
static components = {};
|
|
17332
|
-
// ---------------------------------------------------------------------------
|
|
17333
|
-
// Getters
|
|
17334
|
-
// ---------------------------------------------------------------------------
|
|
17335
|
-
get figureId() {
|
|
17336
|
-
return this.props.figureUI.id;
|
|
17337
|
-
}
|
|
17338
|
-
get getImagePath() {
|
|
17339
|
-
return this.env.model.getters.getImagePath(this.figureId);
|
|
17340
|
-
}
|
|
17341
|
-
}
|
|
17342
|
-
|
|
17343
|
-
const macRegex = /Mac/i;
|
|
17344
|
-
const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
17345
|
-
/**
|
|
17346
|
-
* Return true if the event was triggered from
|
|
17347
|
-
* a child element.
|
|
17348
|
-
*/
|
|
17349
|
-
function isChildEvent(parent, ev) {
|
|
17350
|
-
if (!parent)
|
|
17351
|
-
return false;
|
|
17352
|
-
return !!ev.target && parent.contains(ev.target);
|
|
17353
|
-
}
|
|
17354
|
-
function gridOverlayPosition() {
|
|
17355
|
-
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
17356
|
-
if (spreadsheetElement) {
|
|
17357
|
-
const { top, left } = spreadsheetElement.getBoundingClientRect();
|
|
17358
|
-
return { top, left };
|
|
17359
|
-
}
|
|
17360
|
-
throw new Error("Can't find spreadsheet position");
|
|
17361
|
-
}
|
|
17362
|
-
function getBoundingRectAsPOJO(el) {
|
|
17363
|
-
const rect = el.getBoundingClientRect();
|
|
17364
|
-
return {
|
|
17365
|
-
x: rect.x,
|
|
17366
|
-
y: rect.y,
|
|
17367
|
-
width: rect.width,
|
|
17368
|
-
height: rect.height,
|
|
17369
|
-
};
|
|
17370
|
-
}
|
|
17371
|
-
/**
|
|
17372
|
-
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
17373
|
-
*/
|
|
17374
|
-
function* iterateChildren(el) {
|
|
17375
|
-
yield el;
|
|
17376
|
-
if (el.hasChildNodes()) {
|
|
17377
|
-
for (const child of el.childNodes) {
|
|
17378
|
-
yield* iterateChildren(child);
|
|
17379
|
-
}
|
|
17380
|
-
}
|
|
17381
|
-
}
|
|
17382
|
-
function getOpenedMenus() {
|
|
17383
|
-
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
17384
|
-
}
|
|
17385
|
-
function getCurrentSelection(el) {
|
|
17386
|
-
const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
|
|
17387
|
-
const startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
|
|
17388
|
-
const endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
|
|
17389
|
-
return {
|
|
17390
|
-
start: startSizeBefore,
|
|
17391
|
-
end: endSizeBefore,
|
|
17392
|
-
};
|
|
17393
|
-
}
|
|
17394
|
-
function getStartAndEndSelection(el) {
|
|
17395
|
-
const selection = document.getSelection();
|
|
17396
|
-
return {
|
|
17397
|
-
startElement: selection.anchorNode || el,
|
|
17398
|
-
startSelectionOffset: selection.anchorOffset,
|
|
17399
|
-
endElement: selection.focusNode || el,
|
|
17400
|
-
endSelectionOffset: selection.focusOffset,
|
|
17401
|
-
};
|
|
17402
|
-
}
|
|
17403
|
-
/**
|
|
17404
|
-
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
17405
|
-
* The selected node is either a Text node or an Element node.
|
|
17406
|
-
*
|
|
17407
|
-
* case 1 -Text node:
|
|
17408
|
-
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
17409
|
-
* content length of all previous nodes.
|
|
17410
|
-
*
|
|
17411
|
-
* case 2 - Element node:
|
|
17412
|
-
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
17413
|
-
* all the nodes prior to the selected node as well as the content of the child node before the offset.
|
|
17414
|
-
*
|
|
17415
|
-
* See the MDN documentation for more details.
|
|
17416
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
17417
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
17418
|
-
*
|
|
17419
|
-
*/
|
|
17420
|
-
function findSelectionIndex(el, nodeToFind, nodeOffset) {
|
|
17421
|
-
let usedCharacters = 0;
|
|
17422
|
-
const it = iterateChildren(el);
|
|
17423
|
-
let current = it.next();
|
|
17424
|
-
let isFirstParagraph = true;
|
|
17425
|
-
while (!current.done && current.value !== nodeToFind) {
|
|
17426
|
-
if (!current.value.hasChildNodes()) {
|
|
17427
|
-
if (current.value.textContent) {
|
|
17428
|
-
usedCharacters += current.value.textContent.length;
|
|
17429
|
-
}
|
|
17430
|
-
}
|
|
17431
|
-
// One new paragraph = one new line character, except for the first paragraph
|
|
17432
|
-
if (current.value.nodeName === "P" ||
|
|
17433
|
-
(current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
|
|
17434
|
-
) {
|
|
17435
|
-
if (isFirstParagraph) {
|
|
17436
|
-
isFirstParagraph = false;
|
|
17437
|
-
}
|
|
17438
|
-
else {
|
|
17439
|
-
usedCharacters++;
|
|
17440
|
-
}
|
|
17441
|
-
}
|
|
17442
|
-
current = it.next();
|
|
17443
|
-
}
|
|
17444
|
-
if (current.value !== nodeToFind) {
|
|
17445
|
-
/** This situation can happen if the code is called while the selection is not currently on the element.
|
|
17446
|
-
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
17447
|
-
*
|
|
17448
|
-
* A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
17449
|
-
*/
|
|
17450
|
-
return 0;
|
|
17451
|
-
}
|
|
17452
|
-
else {
|
|
17453
|
-
if (!current.value.hasChildNodes()) {
|
|
17454
|
-
usedCharacters += nodeOffset;
|
|
17455
|
-
}
|
|
17456
|
-
else {
|
|
17457
|
-
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
17458
|
-
usedCharacters += children.reduce((acc, child, index) => {
|
|
17459
|
-
if (child.textContent !== null) {
|
|
17460
|
-
// need to account for paragraph nodes that implicitly add a new line
|
|
17461
|
-
// except for the last paragraph
|
|
17462
|
-
let chars = child.textContent.length;
|
|
17463
|
-
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
17464
|
-
chars++;
|
|
17465
|
-
}
|
|
17466
|
-
return acc + chars;
|
|
17467
|
-
}
|
|
17468
|
-
else {
|
|
17469
|
-
return acc;
|
|
17470
|
-
}
|
|
17471
|
-
}, 0);
|
|
17472
|
-
}
|
|
17473
|
-
}
|
|
17474
|
-
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
17475
|
-
usedCharacters++;
|
|
17476
|
-
}
|
|
17477
|
-
return usedCharacters;
|
|
17478
|
-
}
|
|
17479
|
-
const letterRegex = /^[a-zA-Z]$/;
|
|
17480
|
-
/**
|
|
17481
|
-
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
17482
|
-
*
|
|
17483
|
-
* @argument ev - The keyboard event to transform
|
|
17484
|
-
* @argument mode - Use either ev.key of ev.code to get the string shortcut
|
|
17485
|
-
*
|
|
17486
|
-
* @example
|
|
17487
|
-
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
17488
|
-
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
17489
|
-
*/
|
|
17490
|
-
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
17491
|
-
let keyDownString = "";
|
|
17492
|
-
if (!MODIFIER_KEYS.includes(ev.key)) {
|
|
17493
|
-
if (isCtrlKey(ev))
|
|
17494
|
-
keyDownString += "Ctrl+";
|
|
17495
|
-
if (ev.altKey)
|
|
17496
|
-
keyDownString += "Alt+";
|
|
17497
|
-
if (ev.shiftKey)
|
|
17498
|
-
keyDownString += "Shift+";
|
|
17499
|
-
}
|
|
17500
|
-
const key = mode === "key" ? ev.key : ev.code;
|
|
17501
|
-
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
17502
|
-
return keyDownString;
|
|
17503
|
-
}
|
|
17504
|
-
function isMacOS() {
|
|
17505
|
-
return Boolean(macRegex.test(navigator.userAgent));
|
|
17506
|
-
}
|
|
17507
|
-
/**
|
|
17508
|
-
* @param {KeyboardEvent | MouseEvent} ev
|
|
17509
|
-
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
17510
|
-
* On Mac, this is the "meta" or "command" key.
|
|
17511
|
-
*/
|
|
17512
|
-
function isCtrlKey(ev) {
|
|
17513
|
-
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
17514
|
-
}
|
|
17515
|
-
/**
|
|
17516
|
-
* @param {MouseEvent} ev - The mouse event.
|
|
17517
|
-
* @returns {boolean} Returns true if the event was triggered by a middle-click
|
|
17518
|
-
* or a Ctrl + Click (Cmd + Click on Mac).
|
|
17519
|
-
*/
|
|
17520
|
-
function isMiddleClickOrCtrlClick(ev) {
|
|
17521
|
-
return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
|
|
17522
|
-
}
|
|
17523
|
-
async function convertImageToPng(imageUrl) {
|
|
17524
|
-
return new Promise((resolve, reject) => {
|
|
17525
|
-
const image = new Image();
|
|
17526
|
-
image.addEventListener("load", () => {
|
|
17527
|
-
const canvas = document.createElement("canvas");
|
|
17528
|
-
canvas.width = image.width;
|
|
17529
|
-
canvas.height = image.height;
|
|
17530
|
-
const ctx = canvas.getContext("2d");
|
|
17531
|
-
ctx?.drawImage(image, 0, 0);
|
|
17532
|
-
canvas.toBlob(resolve, "image/png");
|
|
17533
|
-
});
|
|
17534
|
-
image.addEventListener("error", reject);
|
|
17535
|
-
image.src = imageUrl;
|
|
17536
|
-
});
|
|
17537
|
-
}
|
|
17538
|
-
function downloadFile(dataUrl, fileName) {
|
|
17539
|
-
const a = document.createElement("a");
|
|
17540
|
-
a.href = dataUrl;
|
|
17541
|
-
a.download = fileName;
|
|
17542
|
-
document.body.appendChild(a);
|
|
17543
|
-
a.click();
|
|
17544
|
-
document.body.removeChild(a);
|
|
17545
|
-
}
|
|
17546
|
-
/**
|
|
17547
|
-
* Detects if the current browser is Firefox
|
|
17548
|
-
*/
|
|
17549
|
-
function isBrowserFirefox() {
|
|
17550
|
-
return /Firefox/i.test(navigator.userAgent);
|
|
17551
|
-
}
|
|
17552
|
-
|
|
17553
17621
|
/**
|
|
17554
17622
|
* Create a function used to create a Chart based on the definition
|
|
17555
17623
|
*/
|
|
@@ -17982,20 +18050,7 @@ function escapeQueryNameSpaces(query) {
|
|
|
17982
18050
|
return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
|
|
17983
18051
|
}
|
|
17984
18052
|
|
|
17985
|
-
|
|
17986
|
-
figureRegistry.add("chart", {
|
|
17987
|
-
Component: ChartFigure,
|
|
17988
|
-
SidePanelComponent: "ChartPanel",
|
|
17989
|
-
menuBuilder: getChartMenu,
|
|
17990
|
-
});
|
|
17991
|
-
figureRegistry.add("image", {
|
|
17992
|
-
Component: ImageFigure,
|
|
17993
|
-
keepRatio: true,
|
|
17994
|
-
minFigSize: 20,
|
|
17995
|
-
borderWidth: 0,
|
|
17996
|
-
menuBuilder: getImageMenuRegistry,
|
|
17997
|
-
});
|
|
17998
|
-
function getChartMenu(figureId, onFigureDeleted, env) {
|
|
18053
|
+
function getChartMenuActions(figureId, onFigureDeleted, env) {
|
|
17999
18054
|
const menuItemSpecs = [
|
|
18000
18055
|
{
|
|
18001
18056
|
id: "edit",
|
|
@@ -18026,7 +18081,9 @@ function getChartMenu(figureId, onFigureDeleted, env) {
|
|
|
18026
18081
|
"text/html": innerHTML,
|
|
18027
18082
|
"image/png": blob,
|
|
18028
18083
|
});
|
|
18084
|
+
env.notifyUser({ sticky: false, type: "success", text: _t("Chart copied to clipboard") });
|
|
18029
18085
|
},
|
|
18086
|
+
isReadonlyAllowed: true,
|
|
18030
18087
|
},
|
|
18031
18088
|
{
|
|
18032
18089
|
id: "download",
|
|
@@ -18041,14 +18098,15 @@ function getChartMenu(figureId, onFigureDeleted, env) {
|
|
|
18041
18098
|
const url = chartToImageUrl(runtime, figure, chartType);
|
|
18042
18099
|
downloadFile(url, "chart");
|
|
18043
18100
|
},
|
|
18101
|
+
isReadonlyAllowed: true,
|
|
18044
18102
|
},
|
|
18045
18103
|
getDeleteMenuItem(figureId, onFigureDeleted, env),
|
|
18046
18104
|
];
|
|
18047
|
-
return createActions(menuItemSpecs);
|
|
18105
|
+
return createActions(menuItemSpecs).filter((action) => env.model.getters.isReadonly() ? action.isReadonlyAllowed : true);
|
|
18048
18106
|
}
|
|
18049
|
-
function
|
|
18107
|
+
function getImageMenuActions(figureId, onFigureDeleted, env) {
|
|
18050
18108
|
const menuItemSpecs = [
|
|
18051
|
-
getCopyMenuItem(figureId, env),
|
|
18109
|
+
getCopyMenuItem(figureId, env, _t("Image copied to clipboard")),
|
|
18052
18110
|
getCutMenuItem(figureId, env),
|
|
18053
18111
|
{
|
|
18054
18112
|
id: "reset_size",
|
|
@@ -18095,7 +18153,7 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
|
|
|
18095
18153
|
];
|
|
18096
18154
|
return createActions(menuItemSpecs);
|
|
18097
18155
|
}
|
|
18098
|
-
function getCopyMenuItem(figureId, env) {
|
|
18156
|
+
function getCopyMenuItem(figureId, env, copiedNotificationMessage) {
|
|
18099
18157
|
return {
|
|
18100
18158
|
id: "copy",
|
|
18101
18159
|
name: _t("Copy"),
|
|
@@ -18106,6 +18164,9 @@ function getCopyMenuItem(figureId, env) {
|
|
|
18106
18164
|
env.model.dispatch("COPY");
|
|
18107
18165
|
const osClipboardContent = await env.model.getters.getClipboardTextAndImageContent();
|
|
18108
18166
|
await env.clipboard.write(osClipboardContent);
|
|
18167
|
+
if (copiedNotificationMessage) {
|
|
18168
|
+
env.notifyUser({ sticky: false, type: "success", text: copiedNotificationMessage });
|
|
18169
|
+
}
|
|
18109
18170
|
},
|
|
18110
18171
|
icon: "o-spreadsheet-Icon.CLIPBOARD",
|
|
18111
18172
|
};
|
|
@@ -18774,6 +18835,153 @@ class Menu extends owl.Component {
|
|
|
18774
18835
|
}
|
|
18775
18836
|
}
|
|
18776
18837
|
|
|
18838
|
+
class ChartDashboardMenu extends owl.Component {
|
|
18839
|
+
static template = "spreadsheet.ChartDashboardMenu";
|
|
18840
|
+
static components = { Menu };
|
|
18841
|
+
static props = { figureUI: Object };
|
|
18842
|
+
originalChartDefinition;
|
|
18843
|
+
menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
|
|
18844
|
+
setup() {
|
|
18845
|
+
super.setup();
|
|
18846
|
+
this.originalChartDefinition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
|
|
18847
|
+
owl.onWillUpdateProps(({ figureUI }) => {
|
|
18848
|
+
if (figureUI.id !== this.props.figureUI.id) {
|
|
18849
|
+
this.originalChartDefinition = this.env.model.getters.getChartDefinition(figureUI.id);
|
|
18850
|
+
}
|
|
18851
|
+
});
|
|
18852
|
+
}
|
|
18853
|
+
getAvailableTypes() {
|
|
18854
|
+
const definition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
|
|
18855
|
+
if (!["line", "bar", "pie"].includes(definition.type)) {
|
|
18856
|
+
return [];
|
|
18857
|
+
}
|
|
18858
|
+
return ["column", "line", "pie"].map((type) => {
|
|
18859
|
+
const item = chartSubtypeRegistry.get(type);
|
|
18860
|
+
return {
|
|
18861
|
+
...item,
|
|
18862
|
+
icon: this.getIconClasses(item.chartType),
|
|
18863
|
+
};
|
|
18864
|
+
});
|
|
18865
|
+
}
|
|
18866
|
+
getIconClasses(type) {
|
|
18867
|
+
if (type.includes("bar")) {
|
|
18868
|
+
return "fa fa-bar-chart";
|
|
18869
|
+
}
|
|
18870
|
+
if (type.includes("line")) {
|
|
18871
|
+
return "fa fa-line-chart";
|
|
18872
|
+
}
|
|
18873
|
+
if (type.includes("pie")) {
|
|
18874
|
+
return "fa fa-pie-chart";
|
|
18875
|
+
}
|
|
18876
|
+
return "";
|
|
18877
|
+
}
|
|
18878
|
+
onTypeChange(type) {
|
|
18879
|
+
const figureId = this.props.figureUI.id;
|
|
18880
|
+
const currentDefinition = this.env.model.getters.getChartDefinition(figureId);
|
|
18881
|
+
if (currentDefinition.type === type) {
|
|
18882
|
+
return;
|
|
18883
|
+
}
|
|
18884
|
+
let definition;
|
|
18885
|
+
if (this.originalChartDefinition.type === type) {
|
|
18886
|
+
definition = this.originalChartDefinition;
|
|
18887
|
+
}
|
|
18888
|
+
else {
|
|
18889
|
+
const newChartInfo = chartSubtypeRegistry.get(type);
|
|
18890
|
+
const ChartClass = chartRegistry.get(newChartInfo.chartType);
|
|
18891
|
+
const chartCreationContext = this.env.model.getters.getContextCreationChart(figureId);
|
|
18892
|
+
if (!chartCreationContext)
|
|
18893
|
+
return;
|
|
18894
|
+
definition = {
|
|
18895
|
+
...ChartClass.getChartDefinitionFromContextCreation(chartCreationContext),
|
|
18896
|
+
...newChartInfo.subtypeDefinition,
|
|
18897
|
+
};
|
|
18898
|
+
}
|
|
18899
|
+
this.env.model.dispatch("UPDATE_CHART", {
|
|
18900
|
+
definition,
|
|
18901
|
+
figureId,
|
|
18902
|
+
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
18903
|
+
});
|
|
18904
|
+
}
|
|
18905
|
+
get selectedChartType() {
|
|
18906
|
+
return this.env.model.getters.getChartDefinition(this.props.figureUI.id).type;
|
|
18907
|
+
}
|
|
18908
|
+
get backgroundColor() {
|
|
18909
|
+
const color = this.env.model.getters.getChartDefinition(this.props.figureUI.id).background;
|
|
18910
|
+
return "background-color: " + (color || BACKGROUND_CHART_COLOR);
|
|
18911
|
+
}
|
|
18912
|
+
openContextMenu(ev) {
|
|
18913
|
+
this.menuState.isOpen = true;
|
|
18914
|
+
this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
|
|
18915
|
+
this.menuState.menuItems = getChartMenuActions(this.props.figureUI.id, () => { }, this.env);
|
|
18916
|
+
}
|
|
18917
|
+
}
|
|
18918
|
+
|
|
18919
|
+
// -----------------------------------------------------------------------------
|
|
18920
|
+
// STYLE
|
|
18921
|
+
// -----------------------------------------------------------------------------
|
|
18922
|
+
css /* scss */ `
|
|
18923
|
+
.o-chart-container {
|
|
18924
|
+
width: 100%;
|
|
18925
|
+
height: 100%;
|
|
18926
|
+
position: relative;
|
|
18927
|
+
}
|
|
18928
|
+
`;
|
|
18929
|
+
class ChartFigure extends owl.Component {
|
|
18930
|
+
static template = "o-spreadsheet-ChartFigure";
|
|
18931
|
+
static props = {
|
|
18932
|
+
figureUI: Object,
|
|
18933
|
+
onFigureDeleted: Function,
|
|
18934
|
+
};
|
|
18935
|
+
static components = { ChartDashboardMenu };
|
|
18936
|
+
onDoubleClick() {
|
|
18937
|
+
this.env.model.dispatch("SELECT_FIGURE", { figureId: this.props.figureUI.id });
|
|
18938
|
+
this.env.openSidePanel("ChartPanel");
|
|
18939
|
+
}
|
|
18940
|
+
get chartType() {
|
|
18941
|
+
return this.env.model.getters.getChartType(this.props.figureUI.id);
|
|
18942
|
+
}
|
|
18943
|
+
get chartComponent() {
|
|
18944
|
+
const type = this.chartType;
|
|
18945
|
+
const component = chartComponentRegistry.get(type);
|
|
18946
|
+
if (!component) {
|
|
18947
|
+
throw new Error(`Component is not defined for type ${type}`);
|
|
18948
|
+
}
|
|
18949
|
+
return component;
|
|
18950
|
+
}
|
|
18951
|
+
}
|
|
18952
|
+
|
|
18953
|
+
class ImageFigure extends owl.Component {
|
|
18954
|
+
static template = "o-spreadsheet-ImageFigure";
|
|
18955
|
+
static props = {
|
|
18956
|
+
figureUI: Object,
|
|
18957
|
+
onFigureDeleted: Function,
|
|
18958
|
+
};
|
|
18959
|
+
static components = {};
|
|
18960
|
+
// ---------------------------------------------------------------------------
|
|
18961
|
+
// Getters
|
|
18962
|
+
// ---------------------------------------------------------------------------
|
|
18963
|
+
get figureId() {
|
|
18964
|
+
return this.props.figureUI.id;
|
|
18965
|
+
}
|
|
18966
|
+
get getImagePath() {
|
|
18967
|
+
return this.env.model.getters.getImagePath(this.figureId);
|
|
18968
|
+
}
|
|
18969
|
+
}
|
|
18970
|
+
|
|
18971
|
+
const figureRegistry = new Registry();
|
|
18972
|
+
figureRegistry.add("chart", {
|
|
18973
|
+
Component: ChartFigure,
|
|
18974
|
+
SidePanelComponent: "ChartPanel",
|
|
18975
|
+
menuBuilder: getChartMenuActions,
|
|
18976
|
+
});
|
|
18977
|
+
figureRegistry.add("image", {
|
|
18978
|
+
Component: ImageFigure,
|
|
18979
|
+
keepRatio: true,
|
|
18980
|
+
minFigSize: 20,
|
|
18981
|
+
borderWidth: 0,
|
|
18982
|
+
menuBuilder: getImageMenuActions,
|
|
18983
|
+
});
|
|
18984
|
+
|
|
18777
18985
|
// -----------------------------------------------------------------------------
|
|
18778
18986
|
// STYLE
|
|
18779
18987
|
// -----------------------------------------------------------------------------
|
|
@@ -19766,10 +19974,13 @@ const duplicateSheet = {
|
|
|
19766
19974
|
name: _t("Duplicate"),
|
|
19767
19975
|
execute: (env) => {
|
|
19768
19976
|
const sheetIdFrom = env.model.getters.getActiveSheetId();
|
|
19977
|
+
const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
|
|
19769
19978
|
const sheetIdTo = env.model.uuidGenerator.smallUuid();
|
|
19979
|
+
const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
|
|
19770
19980
|
env.model.dispatch("DUPLICATE_SHEET", {
|
|
19771
19981
|
sheetId: sheetIdFrom,
|
|
19772
19982
|
sheetIdTo,
|
|
19983
|
+
sheetNameTo,
|
|
19773
19984
|
});
|
|
19774
19985
|
env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
|
|
19775
19986
|
},
|
|
@@ -19829,7 +20040,7 @@ class MenuItemRegistry extends Registry {
|
|
|
19829
20040
|
/**
|
|
19830
20041
|
* @override
|
|
19831
20042
|
*/
|
|
19832
|
-
|
|
20043
|
+
replace(key, value) {
|
|
19833
20044
|
if (value.id === undefined) {
|
|
19834
20045
|
value.id = key;
|
|
19835
20046
|
}
|
|
@@ -19841,7 +20052,13 @@ class MenuItemRegistry extends Registry {
|
|
|
19841
20052
|
* @param path Path of items to add this subitem
|
|
19842
20053
|
* @param value Subitem to add
|
|
19843
20054
|
*/
|
|
19844
|
-
addChild(key, path, value
|
|
20055
|
+
addChild(key, path, value) {
|
|
20056
|
+
return this._replaceChild(key, path, value, { force: false });
|
|
20057
|
+
}
|
|
20058
|
+
replaceChild(key, path, value) {
|
|
20059
|
+
return this._replaceChild(key, path, value, { force: true });
|
|
20060
|
+
}
|
|
20061
|
+
_replaceChild(key, path, value, options = { force: true }) {
|
|
19845
20062
|
if (typeof value !== "function" && value.id === undefined) {
|
|
19846
20063
|
value.id = key;
|
|
19847
20064
|
}
|
|
@@ -24889,6 +25106,7 @@ function repairInitialMessages(data, initialMessages) {
|
|
|
24889
25106
|
initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
|
|
24890
25107
|
initialMessages = fixChartDefinitions(data, initialMessages);
|
|
24891
25108
|
initialMessages = fixFigureOffset(data, initialMessages);
|
|
25109
|
+
initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
|
|
24892
25110
|
return initialMessages;
|
|
24893
25111
|
}
|
|
24894
25112
|
/**
|
|
@@ -25021,6 +25239,42 @@ function fixFigureOffset(data, messages) {
|
|
|
25021
25239
|
}
|
|
25022
25240
|
return messages;
|
|
25023
25241
|
}
|
|
25242
|
+
function fixTranslatedDuplicateSheetName(data, initialMessages) {
|
|
25243
|
+
const sheetNames = {};
|
|
25244
|
+
for (const sheet of data.sheets || []) {
|
|
25245
|
+
sheetNames[sheet.id] = sheet.name;
|
|
25246
|
+
}
|
|
25247
|
+
const messages = [];
|
|
25248
|
+
for (const message of initialMessages) {
|
|
25249
|
+
if (message.type === "REMOTE_REVISION") {
|
|
25250
|
+
const commands = [];
|
|
25251
|
+
for (const cmd of message.commands) {
|
|
25252
|
+
switch (cmd.type) {
|
|
25253
|
+
case "DUPLICATE_SHEET":
|
|
25254
|
+
cmd.sheetNameTo =
|
|
25255
|
+
cmd.sheetNameTo ??
|
|
25256
|
+
getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
|
|
25257
|
+
break;
|
|
25258
|
+
case "CREATE_SHEET":
|
|
25259
|
+
sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
|
|
25260
|
+
break;
|
|
25261
|
+
case "RENAME_SHEET":
|
|
25262
|
+
sheetNames[cmd.sheetId] = cmd.newName || getNextSheetName(Object.values(sheetNames));
|
|
25263
|
+
break;
|
|
25264
|
+
}
|
|
25265
|
+
commands.push(cmd);
|
|
25266
|
+
}
|
|
25267
|
+
messages.push({
|
|
25268
|
+
...message,
|
|
25269
|
+
commands,
|
|
25270
|
+
});
|
|
25271
|
+
}
|
|
25272
|
+
else {
|
|
25273
|
+
messages.push(message);
|
|
25274
|
+
}
|
|
25275
|
+
}
|
|
25276
|
+
return initialMessages;
|
|
25277
|
+
}
|
|
25024
25278
|
// -----------------------------------------------------------------------------
|
|
25025
25279
|
// Helpers
|
|
25026
25280
|
// -----------------------------------------------------------------------------
|
|
@@ -26874,29 +27128,6 @@ function validateArguments(descr) {
|
|
|
26874
27128
|
}
|
|
26875
27129
|
}
|
|
26876
27130
|
|
|
26877
|
-
function assertSingleColOrRow(errorStr, arg) {
|
|
26878
|
-
assert(() => arg.length === 1 || arg[0].length === 1, errorStr);
|
|
26879
|
-
}
|
|
26880
|
-
function assertSameDimensions(errorStr, ...args) {
|
|
26881
|
-
if (args.every(isMatrix)) {
|
|
26882
|
-
const cols = args[0].length;
|
|
26883
|
-
const rows = args[0][0].length;
|
|
26884
|
-
for (const arg of args) {
|
|
26885
|
-
assert(() => arg.length === cols && arg[0].length === rows, errorStr);
|
|
26886
|
-
}
|
|
26887
|
-
return;
|
|
26888
|
-
}
|
|
26889
|
-
if (args.some((arg) => Array.isArray(arg) && (arg.length !== 1 || arg[0].length !== 1))) {
|
|
26890
|
-
throw new EvaluationError(errorStr);
|
|
26891
|
-
}
|
|
26892
|
-
}
|
|
26893
|
-
function assertPositive(errorStr, arg) {
|
|
26894
|
-
assert(() => arg > 0, errorStr);
|
|
26895
|
-
}
|
|
26896
|
-
function assertSquareMatrix(errorStr, arg) {
|
|
26897
|
-
assert(() => arg.length === arg[0].length, errorStr);
|
|
26898
|
-
}
|
|
26899
|
-
|
|
26900
27131
|
// -----------------------------------------------------------------------------
|
|
26901
27132
|
// ARRAY_CONSTRAIN
|
|
26902
27133
|
// -----------------------------------------------------------------------------
|
|
@@ -26911,8 +27142,12 @@ const ARRAY_CONSTRAIN = {
|
|
|
26911
27142
|
const _array = toMatrix(array);
|
|
26912
27143
|
const _rowsArg = toInteger(rows?.value, this.locale);
|
|
26913
27144
|
const _columnsArg = toInteger(columns?.value, this.locale);
|
|
26914
|
-
|
|
26915
|
-
|
|
27145
|
+
if (_rowsArg <= 0) {
|
|
27146
|
+
return new EvaluationError(_t("The rows argument (%s) must be strictly positive.", _rowsArg.toString()));
|
|
27147
|
+
}
|
|
27148
|
+
if (_columnsArg <= 0) {
|
|
27149
|
+
return new EvaluationError(_t("The columns argument (%s) must be strictly positive.", _columnsArg.toString()));
|
|
27150
|
+
}
|
|
26916
27151
|
const _nbRows = Math.min(_rowsArg, _array[0].length);
|
|
26917
27152
|
const _nbColumns = Math.min(_columnsArg, _array.length);
|
|
26918
27153
|
return generateMatrix(_nbColumns, _nbRows, (col, row) => _array[col][row]);
|
|
@@ -26933,7 +27168,9 @@ const CHOOSECOLS = {
|
|
|
26933
27168
|
const _array = toMatrix(array);
|
|
26934
27169
|
const _columns = flattenRowFirst(columns, (item) => toInteger(item?.value, this.locale));
|
|
26935
27170
|
const argOutOfRange = _columns.filter((col) => col === 0 || _array.length < Math.abs(col));
|
|
26936
|
-
|
|
27171
|
+
if (argOutOfRange.length !== 0) {
|
|
27172
|
+
return new EvaluationError(_t("The columns arguments must be between -%s and %s (got %s), excluding 0.", _array.length.toString(), _array.length.toString(), argOutOfRange.join(",")));
|
|
27173
|
+
}
|
|
26937
27174
|
const result = Array(_columns.length);
|
|
26938
27175
|
for (let col = 0; col < _columns.length; col++) {
|
|
26939
27176
|
if (_columns[col] > 0) {
|
|
@@ -26962,7 +27199,9 @@ const CHOOSEROWS = {
|
|
|
26962
27199
|
const _rows = flattenRowFirst(rows, (item) => toInteger(item?.value, this.locale));
|
|
26963
27200
|
const _nbColumns = _array.length;
|
|
26964
27201
|
const argOutOfRange = _rows.filter((row) => row === 0 || _array[0].length < Math.abs(row));
|
|
26965
|
-
|
|
27202
|
+
if (argOutOfRange.length !== 0) {
|
|
27203
|
+
return new EvaluationError(_t("The rows arguments must be between -%s and %s (got %s), excluding 0.", _array[0].length.toString(), _array[0].length.toString(), argOutOfRange.join(",")));
|
|
27204
|
+
}
|
|
26966
27205
|
return generateMatrix(_nbColumns, _rows.length, (col, row) => {
|
|
26967
27206
|
if (_rows[row] > 0) {
|
|
26968
27207
|
return _array[col][_rows[row] - 1]; // -1 because columns arguments are 1-indexed
|
|
@@ -26988,8 +27227,12 @@ const EXPAND = {
|
|
|
26988
27227
|
const _array = toMatrix(arg);
|
|
26989
27228
|
const _nbRows = toInteger(rows?.value, this.locale);
|
|
26990
27229
|
const _nbColumns = columns !== undefined ? toInteger(columns.value, this.locale) : _array.length;
|
|
26991
|
-
|
|
26992
|
-
|
|
27230
|
+
if (_nbRows < _array[0].length) {
|
|
27231
|
+
return new EvaluationError(_t("The rows arguments (%s) must be greater or equal than the number of rows of the array.", _nbRows.toString()));
|
|
27232
|
+
}
|
|
27233
|
+
if (_nbColumns < _array.length) {
|
|
27234
|
+
return new EvaluationError(_t("The columns arguments (%s) must be greater or equal than the number of columns of the array.", _nbColumns.toString()));
|
|
27235
|
+
}
|
|
26993
27236
|
return generateMatrix(_nbColumns, _nbRows, (col, row) => col >= _array.length || row >= _array[col].length ? padWith : _array[col][row]);
|
|
26994
27237
|
},
|
|
26995
27238
|
isExported: true,
|
|
@@ -27092,7 +27335,9 @@ const MDETERM = {
|
|
|
27092
27335
|
],
|
|
27093
27336
|
compute: function (matrix) {
|
|
27094
27337
|
const _matrix = toNumberMatrix(matrix, "square_matrix");
|
|
27095
|
-
|
|
27338
|
+
if (!isSquareMatrix(_matrix)) {
|
|
27339
|
+
return new EvaluationError(_t("The argument square_matrix must have the same number of columns and rows."));
|
|
27340
|
+
}
|
|
27096
27341
|
return invertMatrix(_matrix).determinant;
|
|
27097
27342
|
},
|
|
27098
27343
|
isExported: true,
|
|
@@ -27107,7 +27352,9 @@ const MINVERSE = {
|
|
|
27107
27352
|
],
|
|
27108
27353
|
compute: function (matrix) {
|
|
27109
27354
|
const _matrix = toNumberMatrix(matrix, "square_matrix");
|
|
27110
|
-
|
|
27355
|
+
if (!isSquareMatrix(_matrix)) {
|
|
27356
|
+
return new EvaluationError(_t("The argument square_matrix must have the same number of columns and rows."));
|
|
27357
|
+
}
|
|
27111
27358
|
const { inverted } = invertMatrix(_matrix);
|
|
27112
27359
|
if (!inverted) {
|
|
27113
27360
|
return new EvaluationError(_t("The matrix is not invertible."));
|
|
@@ -27128,8 +27375,10 @@ const MMULT = {
|
|
|
27128
27375
|
compute: function (matrix1, matrix2) {
|
|
27129
27376
|
const _matrix1 = toNumberMatrix(matrix1, "matrix1");
|
|
27130
27377
|
const _matrix2 = toNumberMatrix(matrix2, "matrix2");
|
|
27131
|
-
|
|
27132
|
-
|
|
27378
|
+
if (_matrix1.length !== _matrix2[0].length) {
|
|
27379
|
+
return new EvaluationError(_t("In [[FUNCTION_NAME]], the number of columns of the first matrix (%s) must be equal to the \
|
|
27380
|
+
number of rows of the second matrix (%s).", _matrix1.length.toString(), _matrix2[0].length.toString()));
|
|
27381
|
+
}
|
|
27133
27382
|
return multiplyMatrices(_matrix1, _matrix2);
|
|
27134
27383
|
},
|
|
27135
27384
|
isExported: true,
|
|
@@ -27144,7 +27393,9 @@ const SUMPRODUCT = {
|
|
|
27144
27393
|
arg("range2 (number, range<number>, repeating)", _t("The other range whose entries will be multiplied with corresponding entries in the other ranges.")),
|
|
27145
27394
|
],
|
|
27146
27395
|
compute: function (...args) {
|
|
27147
|
-
|
|
27396
|
+
if (!areSameDimensions(...args)) {
|
|
27397
|
+
return new EvaluationError(_t("All the ranges must have the same dimensions."));
|
|
27398
|
+
}
|
|
27148
27399
|
const _args = args.map(toMatrix);
|
|
27149
27400
|
let result = 0;
|
|
27150
27401
|
for (let col = 0; col < _args[0].length; col++) {
|
|
@@ -27172,7 +27423,9 @@ const SUMPRODUCT = {
|
|
|
27172
27423
|
* Ignore the pairs X,Y where one of the value isn't a number. Throw an error if no pair of numbers is found.
|
|
27173
27424
|
*/
|
|
27174
27425
|
function getSumXAndY(arrayX, arrayY, cb) {
|
|
27175
|
-
|
|
27426
|
+
if (!areSameDimensions(arrayX, arrayY)) {
|
|
27427
|
+
return new EvaluationError(_t("The arguments array_x and array_y must have the same dimensions."));
|
|
27428
|
+
}
|
|
27176
27429
|
const _arrayX = toMatrix(arrayX);
|
|
27177
27430
|
const _arrayY = toMatrix(arrayY);
|
|
27178
27431
|
let validPairFound = false;
|
|
@@ -27354,7 +27607,9 @@ const WRAPCOLS = {
|
|
|
27354
27607
|
compute: function (range, wrapCount, padWith = { value: 0 }) {
|
|
27355
27608
|
const _array = toMatrix(range);
|
|
27356
27609
|
const nbRows = toInteger(wrapCount?.value, this.locale);
|
|
27357
|
-
|
|
27610
|
+
if (!isSingleColOrRow(_array)) {
|
|
27611
|
+
return new EvaluationError(_t("Argument range must be a single row or column."));
|
|
27612
|
+
}
|
|
27358
27613
|
const array = _array.flat();
|
|
27359
27614
|
const nbColumns = Math.ceil(array.length / nbRows);
|
|
27360
27615
|
return generateMatrix(nbColumns, nbRows, (col, row) => {
|
|
@@ -27378,7 +27633,9 @@ const WRAPROWS = {
|
|
|
27378
27633
|
compute: function (range, wrapCount, padWith = { value: 0 }) {
|
|
27379
27634
|
const _array = toMatrix(range);
|
|
27380
27635
|
const nbColumns = toInteger(wrapCount?.value, this.locale);
|
|
27381
|
-
|
|
27636
|
+
if (!isSingleColOrRow(_array)) {
|
|
27637
|
+
return new EvaluationError(_t("Argument range must be a single row or column."));
|
|
27638
|
+
}
|
|
27382
27639
|
const array = _array.flat();
|
|
27383
27640
|
const nbRows = Math.ceil(array.length / nbColumns);
|
|
27384
27641
|
return generateMatrix(nbColumns, nbRows, (col, row) => {
|
|
@@ -27461,7 +27718,9 @@ const ACOS = {
|
|
|
27461
27718
|
],
|
|
27462
27719
|
compute: function (value) {
|
|
27463
27720
|
const _value = toNumber(value, this.locale);
|
|
27464
|
-
|
|
27721
|
+
if (Math.abs(_value) > 1) {
|
|
27722
|
+
return new EvaluationError(_t("The value (%s) must be between -1 and 1 inclusive.", _value));
|
|
27723
|
+
}
|
|
27465
27724
|
return Math.acos(_value);
|
|
27466
27725
|
},
|
|
27467
27726
|
isExported: true,
|
|
@@ -27476,7 +27735,9 @@ const ACOSH = {
|
|
|
27476
27735
|
],
|
|
27477
27736
|
compute: function (value) {
|
|
27478
27737
|
const _value = toNumber(value, this.locale);
|
|
27479
|
-
|
|
27738
|
+
if (_value < 1) {
|
|
27739
|
+
return new EvaluationError(_t("The value (%s) must be greater than or equal to 1.", _value));
|
|
27740
|
+
}
|
|
27480
27741
|
return Math.acosh(_value);
|
|
27481
27742
|
},
|
|
27482
27743
|
isExported: true,
|
|
@@ -27507,7 +27768,9 @@ const ACOTH = {
|
|
|
27507
27768
|
],
|
|
27508
27769
|
compute: function (value) {
|
|
27509
27770
|
const _value = toNumber(value, this.locale);
|
|
27510
|
-
|
|
27771
|
+
if (Math.abs(_value) <= 1) {
|
|
27772
|
+
return new EvaluationError(_t("The value (%s) cannot be between -1 and 1 inclusive.", _value));
|
|
27773
|
+
}
|
|
27511
27774
|
return Math.log((_value + 1) / (_value - 1)) / 2;
|
|
27512
27775
|
},
|
|
27513
27776
|
isExported: true,
|
|
@@ -27522,7 +27785,9 @@ const ASIN = {
|
|
|
27522
27785
|
],
|
|
27523
27786
|
compute: function (value) {
|
|
27524
27787
|
const _value = toNumber(value, this.locale);
|
|
27525
|
-
|
|
27788
|
+
if (Math.abs(_value) > 1) {
|
|
27789
|
+
return new EvaluationError(_t("The value (%s) must be between -1 and 1 inclusive.", _value));
|
|
27790
|
+
}
|
|
27526
27791
|
return Math.asin(_value);
|
|
27527
27792
|
},
|
|
27528
27793
|
isExported: true,
|
|
@@ -27563,7 +27828,9 @@ const ATAN2 = {
|
|
|
27563
27828
|
compute: function (x, y) {
|
|
27564
27829
|
const _x = toNumber(x, this.locale);
|
|
27565
27830
|
const _y = toNumber(y, this.locale);
|
|
27566
|
-
|
|
27831
|
+
if (_x === 0 && _y === 0) {
|
|
27832
|
+
return new DivisionByZeroError(_t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
27833
|
+
}
|
|
27567
27834
|
return Math.atan2(_y, _x);
|
|
27568
27835
|
},
|
|
27569
27836
|
isExported: true,
|
|
@@ -27578,7 +27845,9 @@ const ATANH = {
|
|
|
27578
27845
|
],
|
|
27579
27846
|
compute: function (value) {
|
|
27580
27847
|
const _value = toNumber(value, this.locale);
|
|
27581
|
-
|
|
27848
|
+
if (Math.abs(_value) >= 1) {
|
|
27849
|
+
return new EvaluationError(_t("The value (%s) must be between -1 and 1 exclusive.", _value));
|
|
27850
|
+
}
|
|
27582
27851
|
return Math.atanh(_value);
|
|
27583
27852
|
},
|
|
27584
27853
|
isExported: true,
|
|
@@ -27595,7 +27864,9 @@ const CEILING = {
|
|
|
27595
27864
|
compute: function (value, factor = { value: DEFAULT_FACTOR }) {
|
|
27596
27865
|
const _value = toNumber(value, this.locale);
|
|
27597
27866
|
const _factor = toNumber(factor, this.locale);
|
|
27598
|
-
|
|
27867
|
+
if (_factor < 0 && _value > 0) {
|
|
27868
|
+
return new EvaluationError(_t("The factor (%s) must be positive when the value (%s) is positive.", _factor, _value));
|
|
27869
|
+
}
|
|
27599
27870
|
return {
|
|
27600
27871
|
value: _factor ? Math.ceil(_value / _factor) * _factor : 0,
|
|
27601
27872
|
format: value?.format,
|
|
@@ -27686,7 +27957,9 @@ const COT = {
|
|
|
27686
27957
|
args: [arg("angle (number)", _t("The angle to find the cotangent of, in radians."))],
|
|
27687
27958
|
compute: function (angle) {
|
|
27688
27959
|
const _angle = toNumber(angle, this.locale);
|
|
27689
|
-
|
|
27960
|
+
if (_angle === 0) {
|
|
27961
|
+
return new DivisionByZeroError(_t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
27962
|
+
}
|
|
27690
27963
|
return 1 / Math.tan(_angle);
|
|
27691
27964
|
},
|
|
27692
27965
|
isExported: true,
|
|
@@ -27699,7 +27972,9 @@ const COTH = {
|
|
|
27699
27972
|
args: [arg("value (number)", _t("Any real value to calculate the hyperbolic cotangent of."))],
|
|
27700
27973
|
compute: function (value) {
|
|
27701
27974
|
const _value = toNumber(value, this.locale);
|
|
27702
|
-
|
|
27975
|
+
if (_value === 0) {
|
|
27976
|
+
return new DivisionByZeroError(_t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
27977
|
+
}
|
|
27703
27978
|
return 1 / Math.tanh(_value);
|
|
27704
27979
|
},
|
|
27705
27980
|
isExported: true,
|
|
@@ -27811,7 +28086,9 @@ const CSC = {
|
|
|
27811
28086
|
args: [arg("angle (number)", _t("The angle to find the cosecant of, in radians."))],
|
|
27812
28087
|
compute: function (angle) {
|
|
27813
28088
|
const _angle = toNumber(angle, this.locale);
|
|
27814
|
-
|
|
28089
|
+
if (_angle === 0) {
|
|
28090
|
+
return new DivisionByZeroError(_t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
28091
|
+
}
|
|
27815
28092
|
return 1 / Math.sin(_angle);
|
|
27816
28093
|
},
|
|
27817
28094
|
isExported: true,
|
|
@@ -27824,7 +28101,9 @@ const CSCH = {
|
|
|
27824
28101
|
args: [arg("value (number)", _t("Any real value to calculate the hyperbolic cosecant of."))],
|
|
27825
28102
|
compute: function (value) {
|
|
27826
28103
|
const _value = toNumber(value, this.locale);
|
|
27827
|
-
|
|
28104
|
+
if (_value === 0) {
|
|
28105
|
+
return new DivisionByZeroError(_t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
28106
|
+
}
|
|
27828
28107
|
return 1 / Math.sinh(_value);
|
|
27829
28108
|
},
|
|
27830
28109
|
isExported: true,
|
|
@@ -27841,7 +28120,9 @@ const DECIMAL = {
|
|
|
27841
28120
|
compute: function (value, base) {
|
|
27842
28121
|
let _base = toNumber(base, this.locale);
|
|
27843
28122
|
_base = Math.floor(_base);
|
|
27844
|
-
|
|
28123
|
+
if (2 > _base || _base > 36) {
|
|
28124
|
+
return new EvaluationError(_t("The base (%s) must be between 2 and 36 inclusive.", _base));
|
|
28125
|
+
}
|
|
27845
28126
|
const _value = toString(value);
|
|
27846
28127
|
if (_value === "") {
|
|
27847
28128
|
return 0;
|
|
@@ -27851,9 +28132,13 @@ const DECIMAL = {
|
|
|
27851
28132
|
* Return error if 'value' is positive.
|
|
27852
28133
|
* Remove '-?' in the next regex to catch this error.
|
|
27853
28134
|
*/
|
|
27854
|
-
|
|
28135
|
+
if (!DECIMAL_REPRESENTATION.test(_value)) {
|
|
28136
|
+
return new EvaluationError(_t("The value (%s) must be a valid base %s representation.", _value, _base));
|
|
28137
|
+
}
|
|
27855
28138
|
const deci = parseInt(_value, _base);
|
|
27856
|
-
|
|
28139
|
+
if (isNaN(deci)) {
|
|
28140
|
+
return new EvaluationError(_t("The value (%s) must be a valid base %s representation.", _value, _base));
|
|
28141
|
+
}
|
|
27857
28142
|
return deci;
|
|
27858
28143
|
},
|
|
27859
28144
|
isExported: true,
|
|
@@ -27892,7 +28177,9 @@ const FLOOR = {
|
|
|
27892
28177
|
compute: function (value, factor = { value: DEFAULT_FACTOR }) {
|
|
27893
28178
|
const _value = toNumber(value, this.locale);
|
|
27894
28179
|
const _factor = toNumber(factor, this.locale);
|
|
27895
|
-
|
|
28180
|
+
if (_factor < 0 && _value > 0) {
|
|
28181
|
+
return new EvaluationError(_t("The factor (%s) must be positive when the value (%s) is positive.", _factor, _value));
|
|
28182
|
+
}
|
|
27896
28183
|
return {
|
|
27897
28184
|
value: _factor ? Math.floor(_value / _factor) * _factor : 0,
|
|
27898
28185
|
format: value?.format,
|
|
@@ -28004,7 +28291,9 @@ const LN = {
|
|
|
28004
28291
|
args: [arg("value (number)", _t("The value for which to calculate the logarithm, base e."))],
|
|
28005
28292
|
compute: function (value) {
|
|
28006
28293
|
const _value = toNumber(value, this.locale);
|
|
28007
|
-
|
|
28294
|
+
if (_value <= 0) {
|
|
28295
|
+
return new EvaluationError(_t("The value (%s) must be strictly positive.", _value));
|
|
28296
|
+
}
|
|
28008
28297
|
return Math.log(_value);
|
|
28009
28298
|
},
|
|
28010
28299
|
isExported: true,
|
|
@@ -28021,9 +28310,15 @@ const LOG = {
|
|
|
28021
28310
|
compute: function (value, base = { value: 10 }) {
|
|
28022
28311
|
const _value = toNumber(value, this.locale);
|
|
28023
28312
|
const _base = toNumber(base, this.locale);
|
|
28024
|
-
|
|
28025
|
-
|
|
28026
|
-
|
|
28313
|
+
if (_value <= 0) {
|
|
28314
|
+
return new EvaluationError(_t("The value (%s) must be strictly positive.", _value));
|
|
28315
|
+
}
|
|
28316
|
+
if (_base <= 0) {
|
|
28317
|
+
return new EvaluationError(_t("The base (%s) must be strictly positive.", _base));
|
|
28318
|
+
}
|
|
28319
|
+
if (_base === 1) {
|
|
28320
|
+
return new EvaluationError(_t("The base must be different from 1."));
|
|
28321
|
+
}
|
|
28027
28322
|
return Math.log10(_value) / Math.log10(_base);
|
|
28028
28323
|
},
|
|
28029
28324
|
isExported: true,
|
|
@@ -28032,7 +28327,7 @@ const LOG = {
|
|
|
28032
28327
|
// MOD
|
|
28033
28328
|
// -----------------------------------------------------------------------------
|
|
28034
28329
|
function mod(dividend, divisor) {
|
|
28035
|
-
|
|
28330
|
+
assertNotZero(divisor, _t("The divisor must be different from 0."));
|
|
28036
28331
|
const modulus = dividend % divisor;
|
|
28037
28332
|
// -42 % 10 = -2 but we want 8, so need the code below
|
|
28038
28333
|
if ((modulus > 0 && divisor < 0) || (modulus < 0 && divisor > 0)) {
|
|
@@ -28066,7 +28361,9 @@ const MUNIT = {
|
|
|
28066
28361
|
],
|
|
28067
28362
|
compute: function (n) {
|
|
28068
28363
|
const _n = toInteger(n, this.locale);
|
|
28069
|
-
|
|
28364
|
+
if (_n < 1) {
|
|
28365
|
+
return new EvaluationError(_t("The argument dimension must be positive"));
|
|
28366
|
+
}
|
|
28070
28367
|
return getUnitMatrix(_n);
|
|
28071
28368
|
},
|
|
28072
28369
|
isExported: true,
|
|
@@ -28111,7 +28408,9 @@ const POWER = {
|
|
|
28111
28408
|
compute: function (base, exponent) {
|
|
28112
28409
|
const _base = toNumber(base, this.locale);
|
|
28113
28410
|
const _exponent = toNumber(exponent, this.locale);
|
|
28114
|
-
|
|
28411
|
+
if (_base < 0 && !Number.isInteger(_exponent)) {
|
|
28412
|
+
return new EvaluationError(_t("The exponent (%s) must be an integer when the base is negative.", _exponent));
|
|
28413
|
+
}
|
|
28115
28414
|
return { value: Math.pow(_base, _exponent), format: base?.format };
|
|
28116
28415
|
},
|
|
28117
28416
|
isExported: true,
|
|
@@ -28184,11 +28483,19 @@ const RANDARRAY = {
|
|
|
28184
28483
|
const _min = toNumber(min, this.locale);
|
|
28185
28484
|
const _max = toNumber(max, this.locale);
|
|
28186
28485
|
const _whole_number = toBoolean(wholeNumber);
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
|
|
28486
|
+
if (_cols < 1) {
|
|
28487
|
+
return new EvaluationError(_t("The number of columns (%s) must be positive.", _cols));
|
|
28488
|
+
}
|
|
28489
|
+
if (_rows < 1) {
|
|
28490
|
+
return new EvaluationError(_t("The number of rows (%s) must be positive.", _rows));
|
|
28491
|
+
}
|
|
28492
|
+
if (_min > _max) {
|
|
28493
|
+
return new EvaluationError(_t("The maximum (%s) must be greater than or equal to the minimum (%s).", _max, _min));
|
|
28494
|
+
}
|
|
28190
28495
|
if (_whole_number) {
|
|
28191
|
-
|
|
28496
|
+
if (!Number.isInteger(_min) || !Number.isInteger(_max)) {
|
|
28497
|
+
return new EvaluationError(_t("The maximum (%s) and minimum (%s) must be integers when whole_number is TRUE.", _max.toString(), _min.toString()));
|
|
28498
|
+
}
|
|
28192
28499
|
}
|
|
28193
28500
|
const result = Array(_cols);
|
|
28194
28501
|
for (let col = 0; col < _cols; col++) {
|
|
@@ -28224,7 +28531,9 @@ const RANDBETWEEN = {
|
|
|
28224
28531
|
if (!Number.isInteger(_high)) {
|
|
28225
28532
|
_high = Math.floor(_high);
|
|
28226
28533
|
}
|
|
28227
|
-
|
|
28534
|
+
if (_low > _high) {
|
|
28535
|
+
return new EvaluationError(_t("The high (%s) must be greater than or equal to the low (%s).", _high, _low));
|
|
28536
|
+
}
|
|
28228
28537
|
return {
|
|
28229
28538
|
value: _low + Math.ceil((_high - _low + 1) * Math.random()) - 1,
|
|
28230
28539
|
format: low?.format,
|
|
@@ -28360,8 +28669,12 @@ const SEQUENCE = {
|
|
|
28360
28669
|
const _step = toNumber(step, this.locale);
|
|
28361
28670
|
const _rows = toInteger(rows, this.locale);
|
|
28362
28671
|
const _columns = toInteger(columns, this.locale);
|
|
28363
|
-
|
|
28364
|
-
|
|
28672
|
+
if (_columns < 1) {
|
|
28673
|
+
return new EvaluationError(_t("The number of columns (%s) must be positive.", _columns));
|
|
28674
|
+
}
|
|
28675
|
+
if (_rows < 1) {
|
|
28676
|
+
return new EvaluationError(_t("The number of rows (%s) must be positive.", _rows));
|
|
28677
|
+
}
|
|
28365
28678
|
return generateMatrix(_columns, _rows, (col, row) => {
|
|
28366
28679
|
return {
|
|
28367
28680
|
value: _start + row * _columns * _step + col * _step,
|
|
@@ -28400,7 +28713,9 @@ const SQRT = {
|
|
|
28400
28713
|
args: [arg("value (number)", _t("The number for which to calculate the positive square root."))],
|
|
28401
28714
|
compute: function (value) {
|
|
28402
28715
|
const _value = toNumber(value, this.locale);
|
|
28403
|
-
|
|
28716
|
+
if (_value < 0) {
|
|
28717
|
+
return new EvaluationError(_t("The value (%s) must be positive or null.", _value));
|
|
28718
|
+
}
|
|
28404
28719
|
return { value: Math.sqrt(_value), format: value?.format };
|
|
28405
28720
|
},
|
|
28406
28721
|
isExported: true,
|
|
@@ -28608,7 +28923,7 @@ function filterAndFlatData(dataY, dataX) {
|
|
|
28608
28923
|
_flatDataX.push(x);
|
|
28609
28924
|
lenX += 1;
|
|
28610
28925
|
});
|
|
28611
|
-
assert(
|
|
28926
|
+
assert(lenY === lenX, _t("[[FUNCTION_NAME]] has mismatched argument count %s vs %s.", lenY, lenX));
|
|
28612
28927
|
const flatDataX = [];
|
|
28613
28928
|
const flatDataY = [];
|
|
28614
28929
|
for (let i = 0; i < lenY; i++) {
|
|
@@ -28625,7 +28940,10 @@ function filterAndFlatData(dataY, dataX) {
|
|
|
28625
28940
|
function covariance(dataY, dataX, isSample) {
|
|
28626
28941
|
const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
|
|
28627
28942
|
const count = flatDataY.length;
|
|
28628
|
-
|
|
28943
|
+
assertNotZero(count);
|
|
28944
|
+
if (isSample) {
|
|
28945
|
+
assertNotZero(count - 1);
|
|
28946
|
+
}
|
|
28629
28947
|
let sumY = 0;
|
|
28630
28948
|
let sumX = 0;
|
|
28631
28949
|
for (let i = 0; i < count; i++) {
|
|
@@ -28647,14 +28965,17 @@ function variance(args, isSample, textAs0, locale) {
|
|
|
28647
28965
|
count += 1;
|
|
28648
28966
|
return acc + a;
|
|
28649
28967
|
}, 0, locale);
|
|
28650
|
-
|
|
28968
|
+
assertNotZero(count);
|
|
28969
|
+
if (isSample) {
|
|
28970
|
+
assertNotZero(count - 1);
|
|
28971
|
+
}
|
|
28651
28972
|
const average = sum / count;
|
|
28652
28973
|
return (reduceFunction(args, (acc, a) => acc + Math.pow(a - average, 2), 0, locale) /
|
|
28653
28974
|
(count - (isSample ? 1 : 0)));
|
|
28654
28975
|
}
|
|
28655
28976
|
function centile(data, percent, isInclusive, locale) {
|
|
28656
28977
|
const _percent = toNumber(percent, locale);
|
|
28657
|
-
assert(
|
|
28978
|
+
assert(isInclusive ? 0 <= _percent && _percent <= 1 : 0 < _percent && _percent < 1, _t("Function [[FUNCTION_NAME]] parameter 2 value is out of range."));
|
|
28658
28979
|
const sortedArray = [];
|
|
28659
28980
|
let index;
|
|
28660
28981
|
let count = 0;
|
|
@@ -28666,10 +28987,10 @@ function centile(data, percent, isInclusive, locale) {
|
|
|
28666
28987
|
count++;
|
|
28667
28988
|
}
|
|
28668
28989
|
});
|
|
28669
|
-
assert(
|
|
28990
|
+
assert(count !== 0, _t("[[FUNCTION_NAME]] has no valid input data."));
|
|
28670
28991
|
if (!isInclusive) {
|
|
28671
28992
|
// 2nd argument must be between 1/(n+1) and n/(n+1) with n the number of data
|
|
28672
|
-
assert(
|
|
28993
|
+
assert(1 / (count + 1) <= _percent && _percent <= count / (count + 1), _t("Function [[FUNCTION_NAME]] parameter 2 value is out of range."));
|
|
28673
28994
|
}
|
|
28674
28995
|
return percentile(sortedArray, _percent, isInclusive);
|
|
28675
28996
|
}
|
|
@@ -28688,7 +29009,9 @@ const AVEDEV = {
|
|
|
28688
29009
|
count += 1;
|
|
28689
29010
|
return acc + a;
|
|
28690
29011
|
}, 0, this.locale);
|
|
28691
|
-
|
|
29012
|
+
if (count === 0) {
|
|
29013
|
+
return new DivisionByZeroError(_t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
29014
|
+
}
|
|
28692
29015
|
const average = sum / count;
|
|
28693
29016
|
return reduceNumbers(values, (acc, a) => acc + Math.abs(average - a), 0, this.locale) / count;
|
|
28694
29017
|
},
|
|
@@ -28730,7 +29053,9 @@ const AVERAGE_WEIGHTED = {
|
|
|
28730
29053
|
for (let n = 0; n < args.length - 1; n += 2) {
|
|
28731
29054
|
const argN = args[n];
|
|
28732
29055
|
const argN1 = args[n + 1];
|
|
28733
|
-
|
|
29056
|
+
if (!areSameDimensions(argN, argN1)) {
|
|
29057
|
+
return new EvaluationError(rangeError);
|
|
29058
|
+
}
|
|
28734
29059
|
if (isMatrix(argN)) {
|
|
28735
29060
|
for (let i = 0; i < argN.length; i++) {
|
|
28736
29061
|
for (let j = 0; j < argN[0].length; j++) {
|
|
@@ -28739,12 +29064,16 @@ const AVERAGE_WEIGHTED = {
|
|
|
28739
29064
|
const valueIsNumber = typeof value === "number";
|
|
28740
29065
|
const weightIsNumber = typeof weight === "number";
|
|
28741
29066
|
if (valueIsNumber && weightIsNumber) {
|
|
28742
|
-
|
|
29067
|
+
if (weight < 0) {
|
|
29068
|
+
return new EvaluationError(negativeWeightError);
|
|
29069
|
+
}
|
|
28743
29070
|
sum += value * weight;
|
|
28744
29071
|
count += weight;
|
|
28745
29072
|
continue;
|
|
28746
29073
|
}
|
|
28747
|
-
|
|
29074
|
+
if (valueIsNumber !== weightIsNumber) {
|
|
29075
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] expects number values."));
|
|
29076
|
+
}
|
|
28748
29077
|
}
|
|
28749
29078
|
}
|
|
28750
29079
|
}
|
|
@@ -28752,13 +29081,17 @@ const AVERAGE_WEIGHTED = {
|
|
|
28752
29081
|
const value = toNumber(argN, this.locale);
|
|
28753
29082
|
const weight = isMatrix(argN1) ? argN1?.[0][0].value : toNumber(argN1, this.locale);
|
|
28754
29083
|
if (typeof weight === "number") {
|
|
28755
|
-
|
|
29084
|
+
if (weight < 0) {
|
|
29085
|
+
return new EvaluationError(negativeWeightError);
|
|
29086
|
+
}
|
|
28756
29087
|
sum += value * weight;
|
|
28757
29088
|
count += weight;
|
|
28758
29089
|
}
|
|
28759
29090
|
}
|
|
28760
29091
|
}
|
|
28761
|
-
|
|
29092
|
+
if (count === 0) {
|
|
29093
|
+
return new DivisionByZeroError(_t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
29094
|
+
}
|
|
28762
29095
|
return { value: sum / count, format: inferFormat(args[0]) };
|
|
28763
29096
|
},
|
|
28764
29097
|
};
|
|
@@ -28777,7 +29110,9 @@ const AVERAGEA = {
|
|
|
28777
29110
|
count += 1;
|
|
28778
29111
|
return acc + a;
|
|
28779
29112
|
}, 0, this.locale);
|
|
28780
|
-
|
|
29113
|
+
if (count === 0) {
|
|
29114
|
+
return new DivisionByZeroError(_t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
29115
|
+
}
|
|
28781
29116
|
return {
|
|
28782
29117
|
value: sum / count,
|
|
28783
29118
|
format: inferFormat(args[0]),
|
|
@@ -28806,7 +29141,9 @@ const AVERAGEIF = {
|
|
|
28806
29141
|
sum += value;
|
|
28807
29142
|
}
|
|
28808
29143
|
}, this.locale);
|
|
28809
|
-
|
|
29144
|
+
if (count === 0) {
|
|
29145
|
+
return new DivisionByZeroError(_t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
29146
|
+
}
|
|
28810
29147
|
return sum / count;
|
|
28811
29148
|
},
|
|
28812
29149
|
isExported: true,
|
|
@@ -28834,7 +29171,9 @@ const AVERAGEIFS = {
|
|
|
28834
29171
|
sum += value;
|
|
28835
29172
|
}
|
|
28836
29173
|
}, this.locale);
|
|
28837
|
-
|
|
29174
|
+
if (count === 0) {
|
|
29175
|
+
return new DivisionByZeroError(_t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
|
|
29176
|
+
}
|
|
28838
29177
|
return sum / count;
|
|
28839
29178
|
},
|
|
28840
29179
|
isExported: true,
|
|
@@ -28984,8 +29323,12 @@ const LARGE = {
|
|
|
28984
29323
|
}
|
|
28985
29324
|
});
|
|
28986
29325
|
const result = largests.shift();
|
|
28987
|
-
|
|
28988
|
-
|
|
29326
|
+
if (result === undefined) {
|
|
29327
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] has no valid input data."));
|
|
29328
|
+
}
|
|
29329
|
+
if (count < _n) {
|
|
29330
|
+
return new EvaluationError(_t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n));
|
|
29331
|
+
}
|
|
28989
29332
|
return result;
|
|
28990
29333
|
},
|
|
28991
29334
|
isExported: true,
|
|
@@ -29465,8 +29808,12 @@ const SMALL = {
|
|
|
29465
29808
|
}
|
|
29466
29809
|
});
|
|
29467
29810
|
const result = largests.pop();
|
|
29468
|
-
|
|
29469
|
-
|
|
29811
|
+
if (result === undefined) {
|
|
29812
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] has no valid input data."));
|
|
29813
|
+
}
|
|
29814
|
+
if (count < _n) {
|
|
29815
|
+
return new EvaluationError(_t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n));
|
|
29816
|
+
}
|
|
29470
29817
|
return result;
|
|
29471
29818
|
},
|
|
29472
29819
|
isExported: true,
|
|
@@ -29893,7 +30240,9 @@ const DGET = {
|
|
|
29893
30240
|
args: databaseArgs,
|
|
29894
30241
|
compute: function (database, field, criteria) {
|
|
29895
30242
|
const cells = getMatchingCells(database, field, criteria, this.locale);
|
|
29896
|
-
|
|
30243
|
+
if (cells.length !== 1) {
|
|
30244
|
+
return new EvaluationError(_t("More than one match found in DGET evaluation."));
|
|
30245
|
+
}
|
|
29897
30246
|
return cells[0];
|
|
29898
30247
|
},
|
|
29899
30248
|
isExported: true,
|
|
@@ -30037,14 +30386,18 @@ const DATE = {
|
|
|
30037
30386
|
const _month = Math.trunc(toNumber(month, this.locale));
|
|
30038
30387
|
const _day = Math.trunc(toNumber(day, this.locale));
|
|
30039
30388
|
// For years less than 0 or greater than 10000, return #ERROR.
|
|
30040
|
-
|
|
30389
|
+
if (_year < 0 || _year > 9999) {
|
|
30390
|
+
return new EvaluationError(_t("The year (%s) must be between 0 and 9999 inclusive.", _year.toString()));
|
|
30391
|
+
}
|
|
30041
30392
|
// Between 0 and 1899, we add that value to 1900 to calculate the year
|
|
30042
30393
|
if (_year < 1900) {
|
|
30043
30394
|
_year += 1900;
|
|
30044
30395
|
}
|
|
30045
30396
|
const jsDate = new DateTime(_year, _month - 1, _day);
|
|
30046
30397
|
const result = jsDateToRoundNumber(jsDate);
|
|
30047
|
-
|
|
30398
|
+
if (result < 0) {
|
|
30399
|
+
return new EvaluationError(_t("The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900."));
|
|
30400
|
+
}
|
|
30048
30401
|
return {
|
|
30049
30402
|
value: result,
|
|
30050
30403
|
format: this.locale.dateFormat,
|
|
@@ -30064,12 +30417,16 @@ const DATEDIF = {
|
|
|
30064
30417
|
],
|
|
30065
30418
|
compute: function (startDate, endDate, unit) {
|
|
30066
30419
|
const _unit = toString(unit).toUpperCase();
|
|
30067
|
-
|
|
30420
|
+
if (!Object.values(TIME_UNIT).includes(_unit)) {
|
|
30421
|
+
return new EvaluationError(expectStringSetError(Object.values(TIME_UNIT), toString(unit)));
|
|
30422
|
+
}
|
|
30068
30423
|
const _startDate = Math.trunc(toNumber(startDate, this.locale));
|
|
30069
30424
|
const _endDate = Math.trunc(toNumber(endDate, this.locale));
|
|
30070
30425
|
const jsStartDate = numberToJsDate(_startDate);
|
|
30071
30426
|
const jsEndDate = numberToJsDate(_endDate);
|
|
30072
|
-
|
|
30427
|
+
if (_endDate < _startDate) {
|
|
30428
|
+
return new EvaluationError(_t("start_date (%s) should be on or before end_date (%s).", jsStartDate.toLocaleDateString(), jsEndDate.toLocaleDateString()));
|
|
30429
|
+
}
|
|
30073
30430
|
switch (_unit) {
|
|
30074
30431
|
case TIME_UNIT.WHOLE_YEARS:
|
|
30075
30432
|
return getTimeDifferenceInWholeYears(jsStartDate, jsEndDate);
|
|
@@ -30117,7 +30474,9 @@ const DATEVALUE = {
|
|
|
30117
30474
|
compute: function (dateString) {
|
|
30118
30475
|
const _dateString = toString(dateString);
|
|
30119
30476
|
const internalDate = parseDateTime(_dateString, this.locale);
|
|
30120
|
-
|
|
30477
|
+
if (internalDate === null) {
|
|
30478
|
+
return new EvaluationError(_t("The date_string (%s) cannot be parsed to date/time.", _dateString.toString()));
|
|
30479
|
+
}
|
|
30121
30480
|
return Math.trunc(internalDate.value);
|
|
30122
30481
|
},
|
|
30123
30482
|
isExported: true,
|
|
@@ -30361,17 +30720,7 @@ function weekendToDayNumber(data) {
|
|
|
30361
30720
|
const weekend = data?.value;
|
|
30362
30721
|
// case "string"
|
|
30363
30722
|
if (typeof weekend === "string") {
|
|
30364
|
-
assert(() =>
|
|
30365
|
-
if (weekend.length !== 7) {
|
|
30366
|
-
return false;
|
|
30367
|
-
}
|
|
30368
|
-
for (const day of weekend) {
|
|
30369
|
-
if (day !== "0" && day !== "1") {
|
|
30370
|
-
return false;
|
|
30371
|
-
}
|
|
30372
|
-
}
|
|
30373
|
-
return true;
|
|
30374
|
-
}, _t('When weekend is a string (%s) it must be composed of "0" or "1".', weekend));
|
|
30723
|
+
assert(weekend.length === 7 && [...weekend].every((c) => c === "0" || c === "1"), _t('When weekend is a string (%s) it must be composed of "0" or "1".', weekend));
|
|
30375
30724
|
const result = [];
|
|
30376
30725
|
for (let i = 0; i < 7; i++) {
|
|
30377
30726
|
if (weekend[i] === "1") {
|
|
@@ -30382,7 +30731,7 @@ function weekendToDayNumber(data) {
|
|
|
30382
30731
|
}
|
|
30383
30732
|
//case "number"
|
|
30384
30733
|
if (typeof weekend === "number") {
|
|
30385
|
-
assert((
|
|
30734
|
+
assert((1 <= weekend && weekend <= 7) || (11 <= weekend && weekend <= 17), _t("The weekend (%s) must be a string or a number in the range 1-7 or 11-17.", weekend.toString()));
|
|
30386
30735
|
// case 1 <= weekend <= 7
|
|
30387
30736
|
if (weekend <= 7) {
|
|
30388
30737
|
// 1 = Saturday/Sunday are weekends
|
|
@@ -30483,7 +30832,9 @@ const TIME = {
|
|
|
30483
30832
|
_hour += Math.floor(_minute / 60);
|
|
30484
30833
|
_minute = (_minute % 60) + (_minute < 0 ? 60 : 0);
|
|
30485
30834
|
_hour %= 24;
|
|
30486
|
-
|
|
30835
|
+
if (_hour < 0) {
|
|
30836
|
+
return new EvaluationError(_t("The function [[FUNCTION_NAME]] result cannot be negative"));
|
|
30837
|
+
}
|
|
30487
30838
|
return {
|
|
30488
30839
|
value: _hour / 24 + _minute / (24 * 60) + _second / (24 * 60 * 60),
|
|
30489
30840
|
format: this.locale.timeFormat,
|
|
@@ -30500,7 +30851,9 @@ const TIMEVALUE = {
|
|
|
30500
30851
|
compute: function (timeString) {
|
|
30501
30852
|
const _timeString = toString(timeString);
|
|
30502
30853
|
const internalDate = parseDateTime(_timeString, this.locale);
|
|
30503
|
-
|
|
30854
|
+
if (internalDate === null) {
|
|
30855
|
+
return new EvaluationError(_t("The time_string (%s) cannot be parsed to date/time.", _timeString));
|
|
30856
|
+
}
|
|
30504
30857
|
const result = internalDate.value - Math.trunc(internalDate.value);
|
|
30505
30858
|
return result < 0 ? 1 + result : result;
|
|
30506
30859
|
},
|
|
@@ -30535,7 +30888,9 @@ const WEEKDAY = {
|
|
|
30535
30888
|
const _date = toJsDate(date, this.locale);
|
|
30536
30889
|
const _type = Math.round(toNumber(type, this.locale));
|
|
30537
30890
|
const m = _date.getDay();
|
|
30538
|
-
|
|
30891
|
+
if (![1, 2, 3].includes(_type)) {
|
|
30892
|
+
return new EvaluationError(_t("The type (%s) must be 1, 2 or 3.", _type));
|
|
30893
|
+
}
|
|
30539
30894
|
if (_type === 1)
|
|
30540
30895
|
return m + 1;
|
|
30541
30896
|
if (_type === 2)
|
|
@@ -30556,7 +30911,9 @@ const WEEKNUM = {
|
|
|
30556
30911
|
compute: function (date, type = { value: DEFAULT_TYPE }) {
|
|
30557
30912
|
const _date = toJsDate(date, this.locale);
|
|
30558
30913
|
const _type = Math.round(toNumber(type, this.locale));
|
|
30559
|
-
|
|
30914
|
+
if (![1, 2, 11, 12, 13, 14, 15, 16, 17, 21].includes(_type)) {
|
|
30915
|
+
return new EvaluationError(_t("The type (%s) is out of range.", _type.toString()));
|
|
30916
|
+
}
|
|
30560
30917
|
if (_type === 21) {
|
|
30561
30918
|
return ISOWEEKNUM.compute.bind(this)(date);
|
|
30562
30919
|
}
|
|
@@ -30612,8 +30969,8 @@ const WORKDAY_INTL = {
|
|
|
30612
30969
|
compute: function (startDate, numDays, weekend = { value: DEFAULT_WEEKEND }, holidays) {
|
|
30613
30970
|
const _startDate = toJsDate(startDate, this.locale);
|
|
30614
30971
|
const _numDays = Math.trunc(toNumber(numDays, this.locale));
|
|
30615
|
-
if (
|
|
30616
|
-
|
|
30972
|
+
if (weekend.value === "1111111") {
|
|
30973
|
+
return new EvaluationError(_t("The weekend must be different from '1111111'."));
|
|
30617
30974
|
}
|
|
30618
30975
|
const daysWeekend = weekendToDayNumber(weekend);
|
|
30619
30976
|
const timesHoliday = new Set();
|
|
@@ -30668,9 +31025,15 @@ const YEARFRAC = {
|
|
|
30668
31025
|
const _startDate = Math.trunc(toNumber(startDate, this.locale));
|
|
30669
31026
|
const _endDate = Math.trunc(toNumber(endDate, this.locale));
|
|
30670
31027
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
30671
|
-
|
|
30672
|
-
|
|
30673
|
-
|
|
31028
|
+
if (_startDate < 0) {
|
|
31029
|
+
return new EvaluationError(_t("The start_date (%s) must be positive or null.", _startDate));
|
|
31030
|
+
}
|
|
31031
|
+
if (_endDate < 0) {
|
|
31032
|
+
return new EvaluationError(_t("The end_date (%s) must be positive or null.", _endDate));
|
|
31033
|
+
}
|
|
31034
|
+
if (0 > _dayCountConvention || _dayCountConvention > 4) {
|
|
31035
|
+
return new EvaluationError(_t("The day_count_convention (%s) must be between 0 and 4 inclusive.", _dayCountConvention));
|
|
31036
|
+
}
|
|
30674
31037
|
return getYearFrac(_startDate, _endDate, _dayCountConvention);
|
|
30675
31038
|
},
|
|
30676
31039
|
};
|
|
@@ -30835,7 +31198,7 @@ var engineering = /*#__PURE__*/Object.freeze({
|
|
|
30835
31198
|
|
|
30836
31199
|
function sortMatrix(matrix, locale, ...criteria) {
|
|
30837
31200
|
for (const [i, value] of criteria.entries()) {
|
|
30838
|
-
assert(
|
|
31201
|
+
assert(value !== undefined, _t("Value for parameter %d is missing, while the function [[FUNCTION_NAME]] expect a number or a range.", i + 1));
|
|
30839
31202
|
}
|
|
30840
31203
|
const sortingOrders = [];
|
|
30841
31204
|
const sortColumns = [];
|
|
@@ -30844,7 +31207,7 @@ function sortMatrix(matrix, locale, ...criteria) {
|
|
|
30844
31207
|
sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "asc" : "desc");
|
|
30845
31208
|
const sortColumn = criteria[i];
|
|
30846
31209
|
if (isMatrix(sortColumn) && (sortColumn.length > 1 || sortColumn[0].length > 1)) {
|
|
30847
|
-
assert(
|
|
31210
|
+
assert(sortColumn.length === 1 && sortColumn[0].length === nRows, _t("Wrong size for %s. Expected a range of size 1x%s. Got %sx%s.", `sort_column${i + 1}`, nRows, sortColumn.length, sortColumn[0].length));
|
|
30848
31211
|
sortColumns.push(sortColumn.flat().map((c) => c.value));
|
|
30849
31212
|
}
|
|
30850
31213
|
else {
|
|
@@ -30909,12 +31272,20 @@ const FILTER = {
|
|
|
30909
31272
|
compute: function (range, ...conditions) {
|
|
30910
31273
|
let _array = toMatrix(range);
|
|
30911
31274
|
const _conditionsMatrices = conditions.map((cond) => matrixMap(toMatrix(cond), (data) => data.value));
|
|
30912
|
-
|
|
30913
|
-
|
|
31275
|
+
for (const c of _conditionsMatrices) {
|
|
31276
|
+
if (!isSingleColOrRow(c)) {
|
|
31277
|
+
return new EvaluationError(_t("The arguments condition must be a single column or row."));
|
|
31278
|
+
}
|
|
31279
|
+
}
|
|
31280
|
+
if (!areSameDimensions(...conditions)) {
|
|
31281
|
+
return new EvaluationError(_t("The arguments conditions must have the same dimensions."));
|
|
31282
|
+
}
|
|
30914
31283
|
const _conditions = _conditionsMatrices.map((c) => c.flat());
|
|
30915
31284
|
const mode = _conditionsMatrices[0].length === 1 ? "row" : "col";
|
|
30916
31285
|
_array = mode === "row" ? transposeMatrix(_array) : _array;
|
|
30917
|
-
|
|
31286
|
+
if (_conditions.some((cond) => cond.length !== _array.length)) {
|
|
31287
|
+
return new EvaluationError(_t("FILTER has mismatched sizes on the range and conditions."));
|
|
31288
|
+
}
|
|
30918
31289
|
const result = [];
|
|
30919
31290
|
for (let i = 0; i < _array.length; i++) {
|
|
30920
31291
|
const row = _array[i];
|
|
@@ -30965,8 +31336,12 @@ const SORTN = {
|
|
|
30965
31336
|
const sortingCriteria = displayTiesMode_sortingCriteria.length % 2 === 0
|
|
30966
31337
|
? displayTiesMode_sortingCriteria
|
|
30967
31338
|
: displayTiesMode_sortingCriteria.slice(1);
|
|
30968
|
-
|
|
30969
|
-
|
|
31339
|
+
if (_n < 0) {
|
|
31340
|
+
return new EvaluationError(_t("Wrong value of 'n'. Expected a positive number. Got %s.", _n));
|
|
31341
|
+
}
|
|
31342
|
+
if (_displayTiesMode < 0 || _displayTiesMode > 3) {
|
|
31343
|
+
return new EvaluationError(_t("Wrong value of 'display_ties_mode'. Expected a positive number between 0 and 3. Got %s.", _displayTiesMode));
|
|
31344
|
+
}
|
|
30970
31345
|
const sortedData = sortMatrix(transposeMatrix(range), this.locale, ...sortingCriteria);
|
|
30971
31346
|
const sameRows = (i, j) => JSON.stringify(sortedData[i].map((c) => c.value)) ===
|
|
30972
31347
|
JSON.stringify(sortedData[j].map((c) => c.value));
|
|
@@ -31071,127 +31446,83 @@ var filter = /*#__PURE__*/Object.freeze({
|
|
|
31071
31446
|
UNIQUE: UNIQUE
|
|
31072
31447
|
});
|
|
31073
31448
|
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31080
|
-
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
|
|
31084
|
-
|
|
31085
|
-
|
|
31086
|
-
|
|
31087
|
-
|
|
31088
|
-
|
|
31089
|
-
|
|
31090
|
-
|
|
31091
|
-
|
|
31092
|
-
|
|
31093
|
-
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
|
|
31097
|
-
|
|
31098
|
-
|
|
31099
|
-
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
|
|
31117
|
-
|
|
31118
|
-
|
|
31119
|
-
|
|
31120
|
-
|
|
31121
|
-
|
|
31122
|
-
|
|
31123
|
-
|
|
31124
|
-
|
|
31125
|
-
}
|
|
31126
|
-
|
|
31127
|
-
|
|
31128
|
-
}
|
|
31129
|
-
|
|
31130
|
-
|
|
31131
|
-
|
|
31132
|
-
|
|
31133
|
-
|
|
31134
|
-
|
|
31135
|
-
|
|
31136
|
-
|
|
31137
|
-
|
|
31138
|
-
function assertDiscountStrictlyPositive(discount) {
|
|
31139
|
-
assert(() => discount > 0, _t("The discount (%s) must be strictly positive.", discount.toString()));
|
|
31449
|
+
const expectCashFlowsAndDatesHaveSameDimension = _t("The cashflow_amounts and cashflow_dates ranges must have the same dimensions.");
|
|
31450
|
+
const expectCashFlowsHavePositiveAndNegativesValues = _t("There must be both positive and negative values in cashflow_amounts.");
|
|
31451
|
+
const expectCostPositiveOrZero = (cost) => _t("The cost (%s) must be positive or null.", cost);
|
|
31452
|
+
const expectCostStrictlyPositive = (cost) => _t("The cost (%s) must be strictly positive.", cost);
|
|
31453
|
+
const expectCouponFrequencyIsValid = (frequency) => _t("The frequency (%s) must be one of %s", frequency, [1, 2, 4].toString());
|
|
31454
|
+
const expectDayCountConventionIsValid = (dayCountConvention) => _t("The day_count_convention (%s) must be between 0 and 4 inclusive.", dayCountConvention);
|
|
31455
|
+
const expectDeprecationFactorStrictlyPositive = (factor) => _t("The depreciation factor (%s) must be strictly positive.", factor);
|
|
31456
|
+
const expectDiscountDifferentFromMinusOne = (discount) => _t("The discount (%s) must be different from -1.", discount);
|
|
31457
|
+
const expectDiscountStrictlyPositive = (discount) => _t("The discount (%s) must be strictly positive.", discount);
|
|
31458
|
+
const expectDiscountStrictlySmallerThanOne = (discount) => _t("The discount (%s) must be smaller than 1.", discount);
|
|
31459
|
+
const expectEffectiveRateStrictlyPositive = (effectiveRate) => _t("The effective_rate (%s) must be strictly positive.", effectiveRate);
|
|
31460
|
+
const expectEndPeriodPositiveOrZero = (endPeriod) => _t("The end_period (%s) must be positive or null.", endPeriod);
|
|
31461
|
+
const expectEndPeriodSmallerOrEqualToLife = (end, life) => _t("The end_period (%(end)s) must be smaller or equal to the life (%(life)s).", { end, life });
|
|
31462
|
+
const expectEveryDateGreaterThanFirstDateOfCashFlowDates = (firstDate) => _t("All the dates should be greater or equal to the first date in cashflow_dates (%s).", firstDate);
|
|
31463
|
+
const expectFirstPeriodSmallerOrEqualLastPeriod = (first, last) => _t("The first_period (%(first)s) must be smaller or equal to the last_period (%(last)s).", {
|
|
31464
|
+
first,
|
|
31465
|
+
last,
|
|
31466
|
+
});
|
|
31467
|
+
const expectFirstPeriodStrictlyPositive = (period) => _t("The first_period (%s) must be strictly positive.", period);
|
|
31468
|
+
const expectFutureValueStrictlyPositive = (pv) => _t("The future_value (%s) must be strictly positive.", pv);
|
|
31469
|
+
const expectInvestmentStrictlyPositive = (investment) => _t("The investment (%s) must be strictly positive.", investment);
|
|
31470
|
+
const expectIssuePositiveOrZero = (issue) => _t("The issue (%s) must be positive or null.", issue);
|
|
31471
|
+
const expectLastPeriodSmallerOrEqualNumberOfPeriods = (last, nPeriods) => _t("The last_period (%(last)s) must be smaller or equal to the number_of_periods (%(nPeriods)s).", { last, nPeriods });
|
|
31472
|
+
const expectLastPeriodStrictlyPositive = (period) => _t("The last_period (%s) must be strictly positive.", period);
|
|
31473
|
+
const expectLifeStrictlyPositive = (life) => _t("The life (%s) must be strictly positive.", life);
|
|
31474
|
+
const expectMaturityStrictlyGreaterThanSettlement = (settlement, maturity) => _t("The maturity (%(maturity)s) must be strictly greater than the settlement (%(settlement)s).", {
|
|
31475
|
+
maturity,
|
|
31476
|
+
settlement,
|
|
31477
|
+
});
|
|
31478
|
+
const expectMonthBetweenOneAndTwelve = (month) => _t("The month (%s) must be between 1 and 12 inclusive.", month);
|
|
31479
|
+
const expectNominalRateStrictlyPositive = (nominalRate) => _t("The nominal_rate (%s) must be strictly positive.", nominalRate);
|
|
31480
|
+
const expectNumberOfPeriodDifferentFromZero = (nPeriods) => _t("The number_of_periods (%s) must be different from zero.", nPeriods);
|
|
31481
|
+
const expectNumberOfPeriodsStrictlyPositive = (nPeriods) => _t("The number_of_periods (%s) must be strictly positive.", nPeriods);
|
|
31482
|
+
const expectPeriodBetweenOneAndNumberOfPeriods = (nPeriods) => _t("The period must be between 1 and number_of_periods (%s)", nPeriods);
|
|
31483
|
+
const expectPeriodLessOrEqualToLifeLimit = (period, lifeLimit) => _t("The period (%(period)s) must be less than or equal to %(lifeLimit)s.", { period, lifeLimit });
|
|
31484
|
+
const expectPeriodPositiveOrZero = (period) => _t("The period (%s) must be positive or null.", period);
|
|
31485
|
+
const expectPeriodsByYearStrictlyPositive = (periodsByYear) => _t("The periods_by_year (%s) must be strictly positive.", periodsByYear);
|
|
31486
|
+
const expectPeriodSmallerOrEqualToLife = (period, life) => _t("The period (%(period)s) must be less than or equal life (%(life)s).", { period, life });
|
|
31487
|
+
const expectPeriodStrictlyPositive = (period) => _t("The period (%s) must be strictly positive.", period);
|
|
31488
|
+
const expectPresentValueStrictlyPositive = (pv) => _t("The present_value (%s) must be strictly positive.", pv);
|
|
31489
|
+
const expectPriceStrictlyPositive = (price) => _t("The price (%s) must be strictly positive.", price);
|
|
31490
|
+
const expectPurchaseDateBeforeFirstPeriodEnd = (purchaseDate, firstPeriodEnd) => _t("The purchase_date (%(purchaseDate)s) must be before the first_period_end (%(firstPeriodEnd)s).", { purchaseDate, firstPeriodEnd });
|
|
31491
|
+
const expectPurchaseDatePositiveOrZero = (purchaseDate) => _t("The purchase_date (%s) must be positive or null.", purchaseDate);
|
|
31492
|
+
const expectRateGuessStrictlyGreaterThanMinusOne = (guess) => _t("The rate_guess (%s) must be strictly greater than -1.", guess);
|
|
31493
|
+
const expectRatePositiveOrZero = (rate) => _t("The rate (%s) must be positive or null.", rate);
|
|
31494
|
+
const expectRateStrictlyPositive = (rate) => _t("The rate (%s) must be strictly positive.", rate);
|
|
31495
|
+
const expectRedemptionStrictlyPositive = (redemption) => _t("The redemption (%s) must be strictly positive.", redemption);
|
|
31496
|
+
const expectSalvagePositiveOrZero = (salvage) => _t("The salvage (%s) must be positive or null.", salvage);
|
|
31497
|
+
const expectSalvageSmallerOrEqualThanCost = (salvage, cost) => _t("The salvage (%(salvage)s) must be smaller or equal than the cost (%(cost)s).", {
|
|
31498
|
+
salvage,
|
|
31499
|
+
cost,
|
|
31500
|
+
});
|
|
31501
|
+
const expectSettlementGreaterOrEqualToIssue = (settlement, issue) => _t("The settlement date (%(settlement)s) must be greater or equal to the issue date (%(issue)s).", { settlement, issue });
|
|
31502
|
+
const expectSettlementLessThanOneYearBeforeMaturity = (settlement, maturity) => _t("The settlement date (%(settlement)s) must at most one year after the maturity date (%(maturity)s).", { settlement, maturity });
|
|
31503
|
+
const expectSettlementStrictlyGreaterThanIssue = (settlement, issue) => _t("The settlement date (%(settlement)s) must be strictly greater than the issue date (%(issue)s).", { settlement, issue });
|
|
31504
|
+
const expectStartPeriodPositiveOrZero = (startPeriod) => _t("The start_period (%s) must be positive or null.", startPeriod);
|
|
31505
|
+
const expectStartPeriodSmallerOrEqualEndPeriod = (start, end) => _t("The start_period (%(start)s) must be smaller or equal to the end_period (%(end)s).", {
|
|
31506
|
+
start,
|
|
31507
|
+
end,
|
|
31508
|
+
});
|
|
31509
|
+
const expectUnitStrictlyPositive = (unit) => _t("The unit (%s) must be strictly positive.", unit);
|
|
31510
|
+
const expectYieldPositiveOrZero = (yeld) => _t("The yield (%s) must be positive or null.", yeld);
|
|
31511
|
+
function havePositiveAndNegativeValues(arrayNumbers) {
|
|
31512
|
+
return arrayNumbers.some((val) => val > 0) && arrayNumbers.some((val) => val < 0);
|
|
31140
31513
|
}
|
|
31141
|
-
function
|
|
31142
|
-
|
|
31514
|
+
function isInvalidDayCountConvention(dayCountConvention) {
|
|
31515
|
+
return ![0, 1, 2, 3, 4].includes(dayCountConvention);
|
|
31143
31516
|
}
|
|
31144
|
-
function
|
|
31145
|
-
|
|
31517
|
+
function isInvalidFrequency(frequency) {
|
|
31518
|
+
return ![1, 2, 4].includes(frequency);
|
|
31146
31519
|
}
|
|
31147
|
-
function
|
|
31520
|
+
function isSettlementLessThanOneYearBeforeMaturity(settlement, maturity, locale) {
|
|
31148
31521
|
const startDate = toJsDate(settlement, locale);
|
|
31149
31522
|
const endDate = toJsDate(maturity, locale);
|
|
31150
31523
|
const startDatePlusOneYear = toJsDate(settlement, locale);
|
|
31151
31524
|
startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
|
|
31152
|
-
|
|
31153
|
-
}
|
|
31154
|
-
/**
|
|
31155
|
-
* Check if the given periods are valid. This will assert :
|
|
31156
|
-
*
|
|
31157
|
-
* - 0 < numberOfPeriods
|
|
31158
|
-
* - 0 < firstPeriod <= lastPeriod
|
|
31159
|
-
* - 0 < lastPeriod <= numberOfPeriods
|
|
31160
|
-
*
|
|
31161
|
-
*/
|
|
31162
|
-
function assertFirstAndLastPeriodsAreValid(firstPeriod, lastPeriod, numberOfPeriods) {
|
|
31163
|
-
assertNumberOfPeriodsStrictlyPositive(numberOfPeriods);
|
|
31164
|
-
assert(() => firstPeriod > 0, _t("The first_period (%s) must be strictly positive.", firstPeriod.toString()));
|
|
31165
|
-
assert(() => lastPeriod > 0, _t("The last_period (%s) must be strictly positive.", lastPeriod.toString()));
|
|
31166
|
-
assert(() => firstPeriod <= lastPeriod, _t("The first_period (%s) must be smaller or equal to the last_period (%s).", firstPeriod.toString(), lastPeriod.toString()));
|
|
31167
|
-
assert(() => lastPeriod <= numberOfPeriods, _t("The last_period (%s) must be smaller or equal to the number_of_periods (%s).", firstPeriod.toString(), numberOfPeriods.toString()));
|
|
31168
|
-
}
|
|
31169
|
-
/**
|
|
31170
|
-
* Check if the given periods are valid. This will assert :
|
|
31171
|
-
*
|
|
31172
|
-
* - 0 < life
|
|
31173
|
-
* - 0 <= startPeriod <= endPeriod
|
|
31174
|
-
* - 0 <= endPeriod <= life
|
|
31175
|
-
*
|
|
31176
|
-
*/
|
|
31177
|
-
function assertStartAndEndPeriodAreValid(startPeriod, endPeriod, life) {
|
|
31178
|
-
assertLifeStrictlyPositive(life);
|
|
31179
|
-
assert(() => startPeriod >= 0, _t("The start_period (%s) must be greater or equal than 0.", startPeriod.toString()));
|
|
31180
|
-
assert(() => endPeriod >= 0, _t("The end_period (%s) must be greater or equal than 0.", endPeriod.toString()));
|
|
31181
|
-
assert(() => startPeriod <= endPeriod, _t("The start_period (%s) must be smaller or equal to the end_period (%s).", startPeriod.toString(), endPeriod.toString()));
|
|
31182
|
-
assert(() => endPeriod <= life, _t("The end_period (%s) must be smaller or equal to the life (%s).", startPeriod.toString(), life.toString()));
|
|
31183
|
-
}
|
|
31184
|
-
function assertRateGuessStrictlyGreaterThanMinusOne(guess) {
|
|
31185
|
-
assert(() => guess > -1, _t("The rate_guess (%s) must be strictly greater than -1.", guess.toString()));
|
|
31186
|
-
}
|
|
31187
|
-
function assertCashFlowsAndDatesHaveSameDimension(cashFlows, dates) {
|
|
31188
|
-
assert(() => cashFlows.length === dates.length && cashFlows[0].length === dates[0].length, _t("The cashflow_amounts and cashflow_dates ranges must have the same dimensions."));
|
|
31189
|
-
}
|
|
31190
|
-
function assertCashFlowsHavePositiveAndNegativesValues(cashFlow) {
|
|
31191
|
-
assert(() => cashFlow.some((val) => val > 0) && cashFlow.some((val) => val < 0), _t("There must be both positive and negative values in cashflow_amounts."));
|
|
31192
|
-
}
|
|
31193
|
-
function assertEveryDateGreaterThanFirstDateOfCashFlowDates(dates) {
|
|
31194
|
-
assert(() => dates.every((date) => date >= dates[0]), _t("All the dates should be greater or equal to the first date in cashflow_dates (%s).", dates[0].toString()));
|
|
31525
|
+
return endDate.getTime() <= startDatePlusOneYear.getTime();
|
|
31195
31526
|
}
|
|
31196
31527
|
|
|
31197
31528
|
const DEFAULT_DAY_COUNT_CONVENTION = 0;
|
|
@@ -31226,7 +31557,7 @@ function newtonMethod(func, derivFunc, startValue, maxIterations, epsMax = 1e-10
|
|
|
31226
31557
|
do {
|
|
31227
31558
|
y = func(x);
|
|
31228
31559
|
if (isNaN(y)) {
|
|
31229
|
-
assert(
|
|
31560
|
+
assert(count < maxIterations && nanFallback !== undefined, _t("Function [[FUNCTION_NAME]] didn't find any result."));
|
|
31230
31561
|
count++;
|
|
31231
31562
|
x = nanFallback(previousFallback);
|
|
31232
31563
|
previousFallback = x;
|
|
@@ -31236,7 +31567,7 @@ function newtonMethod(func, derivFunc, startValue, maxIterations, epsMax = 1e-10
|
|
|
31236
31567
|
xDelta = Math.abs(newX - x);
|
|
31237
31568
|
x = newX;
|
|
31238
31569
|
yEqual0 = xDelta < epsMax || Math.abs(y) < epsMax;
|
|
31239
|
-
assert(
|
|
31570
|
+
assert(count < maxIterations, _t("Function [[FUNCTION_NAME]] didn't find any result."));
|
|
31240
31571
|
count++;
|
|
31241
31572
|
} while (!yEqual0);
|
|
31242
31573
|
return x;
|
|
@@ -31259,11 +31590,21 @@ const ACCRINTM = {
|
|
|
31259
31590
|
const _redemption = toNumber(redemption, this.locale);
|
|
31260
31591
|
const _rate = toNumber(rate, this.locale);
|
|
31261
31592
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31262
|
-
|
|
31263
|
-
|
|
31264
|
-
|
|
31265
|
-
|
|
31266
|
-
|
|
31593
|
+
if (start < 0) {
|
|
31594
|
+
return new EvaluationError(expectIssuePositiveOrZero(start));
|
|
31595
|
+
}
|
|
31596
|
+
if (start >= end) {
|
|
31597
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31598
|
+
}
|
|
31599
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31600
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31601
|
+
}
|
|
31602
|
+
if (_redemption <= 0) {
|
|
31603
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
31604
|
+
}
|
|
31605
|
+
if (_rate <= 0) {
|
|
31606
|
+
return new EvaluationError(expectRateStrictlyPositive(_rate));
|
|
31607
|
+
}
|
|
31267
31608
|
const yearFrac = getYearFrac(start, end, _dayCountConvention);
|
|
31268
31609
|
return _redemption * _rate * yearFrac;
|
|
31269
31610
|
},
|
|
@@ -31292,14 +31633,30 @@ const AMORLINC = {
|
|
|
31292
31633
|
const _period = toNumber(period, this.locale);
|
|
31293
31634
|
const _rate = toNumber(rate, this.locale);
|
|
31294
31635
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31636
|
+
if (_cost <= 0) {
|
|
31637
|
+
return new EvaluationError(expectCostStrictlyPositive(_cost));
|
|
31638
|
+
}
|
|
31639
|
+
if (_purchaseDate < 0) {
|
|
31640
|
+
return new EvaluationError(expectPurchaseDatePositiveOrZero(_purchaseDate));
|
|
31641
|
+
}
|
|
31642
|
+
if (_salvage < 0) {
|
|
31643
|
+
return new EvaluationError(expectSalvagePositiveOrZero(_salvage));
|
|
31644
|
+
}
|
|
31645
|
+
if (_salvage > _cost) {
|
|
31646
|
+
return new EvaluationError(expectSalvageSmallerOrEqualThanCost(_salvage, _cost));
|
|
31647
|
+
}
|
|
31648
|
+
if (_period < 0) {
|
|
31649
|
+
return new EvaluationError(expectPeriodPositiveOrZero(_period));
|
|
31650
|
+
}
|
|
31651
|
+
if (_rate <= 0) {
|
|
31652
|
+
return new EvaluationError(expectRateStrictlyPositive(_rate));
|
|
31653
|
+
}
|
|
31654
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31655
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31656
|
+
}
|
|
31657
|
+
if (_purchaseDate > _firstPeriodEnd) {
|
|
31658
|
+
return new EvaluationError(expectPurchaseDateBeforeFirstPeriodEnd(_purchaseDate, _firstPeriodEnd));
|
|
31659
|
+
}
|
|
31303
31660
|
/**
|
|
31304
31661
|
* https://wiki.documentfoundation.org/Documentation/Calc_Functions/AMORLINC
|
|
31305
31662
|
*
|
|
@@ -31337,14 +31694,20 @@ const COUPDAYS = {
|
|
|
31337
31694
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31338
31695
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31339
31696
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31697
|
+
if (start >= end) {
|
|
31698
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31699
|
+
}
|
|
31700
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31701
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31702
|
+
}
|
|
31703
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31704
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31705
|
+
}
|
|
31343
31706
|
// https://wiki.documentfoundation.org/Documentation/Calc_Functions/COUPDAYS
|
|
31344
31707
|
if (_dayCountConvention === 1) {
|
|
31345
31708
|
const before = COUPPCD.compute.bind(this)(settlement, maturity, frequency, dayCountConvention).value;
|
|
31346
31709
|
const after = COUPNCD.compute.bind(this)(settlement, maturity, frequency, dayCountConvention).value;
|
|
31347
|
-
return after - before;
|
|
31710
|
+
return toNumber(after, this.locale) - toNumber(before, this.locale);
|
|
31348
31711
|
}
|
|
31349
31712
|
const daysInYear = _dayCountConvention === 3 ? 365 : 360;
|
|
31350
31713
|
return daysInYear / _frequency;
|
|
@@ -31363,19 +31726,26 @@ const COUPDAYBS = {
|
|
|
31363
31726
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31364
31727
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31365
31728
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
|
|
31729
|
+
if (start >= end) {
|
|
31730
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31731
|
+
}
|
|
31732
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31733
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31734
|
+
}
|
|
31735
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31736
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31737
|
+
}
|
|
31369
31738
|
const couponBeforeStart = COUPPCD.compute.bind(this)(settlement, maturity, frequency, dayCountConvention).value;
|
|
31739
|
+
const _couponBeforeStart = toNumber(couponBeforeStart, this.locale);
|
|
31370
31740
|
if ([1, 2, 3].includes(_dayCountConvention)) {
|
|
31371
|
-
return start -
|
|
31741
|
+
return start - _couponBeforeStart;
|
|
31372
31742
|
}
|
|
31373
31743
|
if (_dayCountConvention === 4) {
|
|
31374
|
-
const yearFrac = getYearFrac(
|
|
31744
|
+
const yearFrac = getYearFrac(_couponBeforeStart, start, _dayCountConvention);
|
|
31375
31745
|
return Math.round(yearFrac * 360);
|
|
31376
31746
|
}
|
|
31377
31747
|
const startDate = toJsDate(start, this.locale);
|
|
31378
|
-
const dateCouponBeforeStart = toJsDate(
|
|
31748
|
+
const dateCouponBeforeStart = toJsDate(_couponBeforeStart, this.locale);
|
|
31379
31749
|
const y1 = dateCouponBeforeStart.getFullYear();
|
|
31380
31750
|
const y2 = startDate.getFullYear();
|
|
31381
31751
|
const m1 = dateCouponBeforeStart.getMonth() + 1; // +1 because months in js start at 0 and it's confusing
|
|
@@ -31419,20 +31789,27 @@ const COUPDAYSNC = {
|
|
|
31419
31789
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31420
31790
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31421
31791
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31792
|
+
if (start >= end) {
|
|
31793
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31794
|
+
}
|
|
31795
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31796
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31797
|
+
}
|
|
31798
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31799
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31800
|
+
}
|
|
31425
31801
|
const couponAfterStart = COUPNCD.compute.bind(this)(settlement, maturity, frequency, dayCountConvention).value;
|
|
31802
|
+
const _couponAfterStart = toNumber(couponAfterStart, this.locale);
|
|
31426
31803
|
if ([1, 2, 3].includes(_dayCountConvention)) {
|
|
31427
|
-
return
|
|
31804
|
+
return _couponAfterStart - start;
|
|
31428
31805
|
}
|
|
31429
31806
|
if (_dayCountConvention === 4) {
|
|
31430
|
-
const yearFrac = getYearFrac(start,
|
|
31807
|
+
const yearFrac = getYearFrac(start, _couponAfterStart, _dayCountConvention);
|
|
31431
31808
|
return Math.round(yearFrac * 360);
|
|
31432
31809
|
}
|
|
31433
31810
|
const coupDayBs = COUPDAYBS.compute.bind(this)(settlement, maturity, frequency, dayCountConvention);
|
|
31434
31811
|
const coupDays = COUPDAYS.compute.bind(this)(settlement, maturity, frequency, dayCountConvention);
|
|
31435
|
-
return coupDays - coupDayBs;
|
|
31812
|
+
return toNumber(coupDays, this.locale) - toNumber(coupDayBs, this.locale);
|
|
31436
31813
|
},
|
|
31437
31814
|
isExported: true,
|
|
31438
31815
|
};
|
|
@@ -31448,12 +31825,18 @@ const COUPNCD = {
|
|
|
31448
31825
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31449
31826
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31450
31827
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31451
|
-
|
|
31452
|
-
|
|
31453
|
-
|
|
31828
|
+
if (start >= end) {
|
|
31829
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31830
|
+
}
|
|
31831
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31832
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31833
|
+
}
|
|
31834
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31835
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31836
|
+
}
|
|
31454
31837
|
const monthsPerPeriod = 12 / _frequency;
|
|
31455
31838
|
const coupNum = COUPNUM.compute.bind(this)(settlement, maturity, frequency, dayCountConvention);
|
|
31456
|
-
const date = addMonthsToDate(toJsDate(end, this.locale), -(coupNum - 1) * monthsPerPeriod, true);
|
|
31839
|
+
const date = addMonthsToDate(toJsDate(end, this.locale), -(toNumber(coupNum, this.locale) - 1) * monthsPerPeriod, true);
|
|
31457
31840
|
return {
|
|
31458
31841
|
value: jsDateToRoundNumber(date),
|
|
31459
31842
|
format: this.locale.dateFormat,
|
|
@@ -31473,9 +31856,15 @@ const COUPNUM = {
|
|
|
31473
31856
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31474
31857
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31475
31858
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31476
|
-
|
|
31477
|
-
|
|
31478
|
-
|
|
31859
|
+
if (start >= end) {
|
|
31860
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31861
|
+
}
|
|
31862
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31863
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31864
|
+
}
|
|
31865
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31866
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31867
|
+
}
|
|
31479
31868
|
let num = 1;
|
|
31480
31869
|
let currentDate = end;
|
|
31481
31870
|
const monthsPerPeriod = 12 / _frequency;
|
|
@@ -31499,9 +31888,15 @@ const COUPPCD = {
|
|
|
31499
31888
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
31500
31889
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31501
31890
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
|
|
31891
|
+
if (start >= end) {
|
|
31892
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
31893
|
+
}
|
|
31894
|
+
if (isInvalidFrequency(_frequency)) {
|
|
31895
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
31896
|
+
}
|
|
31897
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
31898
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
31899
|
+
}
|
|
31505
31900
|
const monthsPerPeriod = 12 / _frequency;
|
|
31506
31901
|
const coupNum = COUPNUM.compute.bind(this)(settlement, maturity, frequency, dayCountConvention);
|
|
31507
31902
|
const date = addMonthsToDate(toJsDate(end, this.locale), -coupNum * monthsPerPeriod, true);
|
|
@@ -31532,9 +31927,27 @@ const CUMIPMT = {
|
|
|
31532
31927
|
const pv = toNumber(presentValue, this.locale);
|
|
31533
31928
|
const n = toNumber(numberOfPeriods, this.locale);
|
|
31534
31929
|
const type = toBoolean(endOrBeginning) ? 1 : 0;
|
|
31535
|
-
|
|
31536
|
-
|
|
31537
|
-
|
|
31930
|
+
if (n <= 0) {
|
|
31931
|
+
return new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
31932
|
+
}
|
|
31933
|
+
if (first <= 0) {
|
|
31934
|
+
return new EvaluationError(expectFirstPeriodStrictlyPositive(first));
|
|
31935
|
+
}
|
|
31936
|
+
if (last <= 0) {
|
|
31937
|
+
return new EvaluationError(expectLastPeriodStrictlyPositive(last));
|
|
31938
|
+
}
|
|
31939
|
+
if (first > last) {
|
|
31940
|
+
return new EvaluationError(expectFirstPeriodSmallerOrEqualLastPeriod(first, last));
|
|
31941
|
+
}
|
|
31942
|
+
if (last > n) {
|
|
31943
|
+
return new EvaluationError(expectLastPeriodSmallerOrEqualNumberOfPeriods(last, n));
|
|
31944
|
+
}
|
|
31945
|
+
if (r <= 0) {
|
|
31946
|
+
return new EvaluationError(expectRateStrictlyPositive(r));
|
|
31947
|
+
}
|
|
31948
|
+
if (pv <= 0) {
|
|
31949
|
+
return new EvaluationError(expectPresentValueStrictlyPositive(pv));
|
|
31950
|
+
}
|
|
31538
31951
|
let cumSum = 0;
|
|
31539
31952
|
for (let i = first; i <= last; i++) {
|
|
31540
31953
|
cumSum += impt(r, i, n, pv, 0, type);
|
|
@@ -31563,9 +31976,27 @@ const CUMPRINC = {
|
|
|
31563
31976
|
const pv = toNumber(presentValue, this.locale);
|
|
31564
31977
|
const n = toNumber(numberOfPeriods, this.locale);
|
|
31565
31978
|
const type = toBoolean(endOrBeginning) ? 1 : 0;
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31979
|
+
if (n <= 0) {
|
|
31980
|
+
return new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
31981
|
+
}
|
|
31982
|
+
if (first <= 0) {
|
|
31983
|
+
return new EvaluationError(expectFirstPeriodStrictlyPositive(first));
|
|
31984
|
+
}
|
|
31985
|
+
if (last <= 0) {
|
|
31986
|
+
return new EvaluationError(expectLastPeriodStrictlyPositive(last));
|
|
31987
|
+
}
|
|
31988
|
+
if (first > last) {
|
|
31989
|
+
return new EvaluationError(expectFirstPeriodSmallerOrEqualLastPeriod(first, last));
|
|
31990
|
+
}
|
|
31991
|
+
if (last > n) {
|
|
31992
|
+
return new EvaluationError(expectLastPeriodSmallerOrEqualNumberOfPeriods(last, n));
|
|
31993
|
+
}
|
|
31994
|
+
if (r <= 0) {
|
|
31995
|
+
return new EvaluationError(expectRateStrictlyPositive(r));
|
|
31996
|
+
}
|
|
31997
|
+
if (pv <= 0) {
|
|
31998
|
+
return new EvaluationError(expectPresentValueStrictlyPositive(pv));
|
|
31999
|
+
}
|
|
31569
32000
|
let cumSum = 0;
|
|
31570
32001
|
for (let i = first; i <= last; i++) {
|
|
31571
32002
|
cumSum += ppmt(r, i, n, pv, 0, type);
|
|
@@ -31594,12 +32025,24 @@ const DB = {
|
|
|
31594
32025
|
const _period = Math.trunc(toNumber(period, this.locale));
|
|
31595
32026
|
const _month = args.length ? Math.trunc(toNumber(args[0], this.locale)) : 12;
|
|
31596
32027
|
const lifeLimit = _life + (_month === 12 ? 0 : 1);
|
|
31597
|
-
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
32028
|
+
if (_cost < 0) {
|
|
32029
|
+
return new EvaluationError(expectCostPositiveOrZero(_cost));
|
|
32030
|
+
}
|
|
32031
|
+
if (_salvage < 0) {
|
|
32032
|
+
return new EvaluationError(expectSalvagePositiveOrZero(_salvage));
|
|
32033
|
+
}
|
|
32034
|
+
if (_period <= 0) {
|
|
32035
|
+
return new EvaluationError(expectPeriodStrictlyPositive(_period));
|
|
32036
|
+
}
|
|
32037
|
+
if (_life <= 0) {
|
|
32038
|
+
return new EvaluationError(expectLifeStrictlyPositive(_life));
|
|
32039
|
+
}
|
|
32040
|
+
if (1 > _month || _month > 12) {
|
|
32041
|
+
return new EvaluationError(expectMonthBetweenOneAndTwelve(_month));
|
|
32042
|
+
}
|
|
32043
|
+
if (_period > lifeLimit) {
|
|
32044
|
+
return new EvaluationError(expectPeriodLessOrEqualToLifeLimit(_period, lifeLimit));
|
|
32045
|
+
}
|
|
31603
32046
|
const monthPart = _month / 12;
|
|
31604
32047
|
let rate = 1 - Math.pow(_salvage / _cost, 1 / _life);
|
|
31605
32048
|
// round to 3 decimal places
|
|
@@ -31624,27 +32067,7 @@ const DB = {
|
|
|
31624
32067
|
// DDB
|
|
31625
32068
|
// -----------------------------------------------------------------------------
|
|
31626
32069
|
const DEFAULT_DDB_DEPRECIATION_FACTOR = 2;
|
|
31627
|
-
|
|
31628
|
-
assertCostPositiveOrZero(cost);
|
|
31629
|
-
assertSalvagePositiveOrZero(salvage);
|
|
31630
|
-
assertPeriodStrictlyPositive(period);
|
|
31631
|
-
assertLifeStrictlyPositive(life);
|
|
31632
|
-
assertPeriodSmallerOrEqualToLife(period, life);
|
|
31633
|
-
assertDeprecationFactorStrictlyPositive(factor);
|
|
31634
|
-
if (cost === 0 || salvage >= cost)
|
|
31635
|
-
return 0;
|
|
31636
|
-
const deprecFactor = factor / life;
|
|
31637
|
-
if (deprecFactor > 1) {
|
|
31638
|
-
return period === 1 ? cost - salvage : 0;
|
|
31639
|
-
}
|
|
31640
|
-
if (period <= 1) {
|
|
31641
|
-
return cost * deprecFactor;
|
|
31642
|
-
}
|
|
31643
|
-
const previousCost = cost * Math.pow(1 - deprecFactor, period - 1);
|
|
31644
|
-
const nextCost = cost * Math.pow(1 - deprecFactor, period);
|
|
31645
|
-
const deprec = nextCost < salvage ? previousCost - salvage : previousCost - nextCost;
|
|
31646
|
-
return Math.max(deprec, 0);
|
|
31647
|
-
}
|
|
32070
|
+
const DEFAULT_DDB_FORMAT = "#,##0.00";
|
|
31648
32071
|
const DDB = {
|
|
31649
32072
|
description: _t("Depreciation via double-declining balance method."),
|
|
31650
32073
|
args: [
|
|
@@ -31660,9 +32083,43 @@ const DDB = {
|
|
|
31660
32083
|
const _life = toNumber(life, this.locale);
|
|
31661
32084
|
const _period = toNumber(period, this.locale);
|
|
31662
32085
|
const _factor = toNumber(factor, this.locale);
|
|
32086
|
+
if (_cost < 0) {
|
|
32087
|
+
return new EvaluationError(expectCostPositiveOrZero(_cost));
|
|
32088
|
+
}
|
|
32089
|
+
if (_salvage < 0) {
|
|
32090
|
+
return new EvaluationError(expectSalvagePositiveOrZero(_salvage));
|
|
32091
|
+
}
|
|
32092
|
+
if (_period <= 0) {
|
|
32093
|
+
return new EvaluationError(expectPeriodStrictlyPositive(_period));
|
|
32094
|
+
}
|
|
32095
|
+
if (_life <= 0) {
|
|
32096
|
+
return new EvaluationError(expectLifeStrictlyPositive(_life));
|
|
32097
|
+
}
|
|
32098
|
+
if (_period > _life) {
|
|
32099
|
+
return new EvaluationError(expectPeriodSmallerOrEqualToLife(_period, _life));
|
|
32100
|
+
}
|
|
32101
|
+
if (_factor <= 0) {
|
|
32102
|
+
return new EvaluationError(expectDeprecationFactorStrictlyPositive(_factor));
|
|
32103
|
+
}
|
|
32104
|
+
if (_cost === 0 || _salvage >= _cost) {
|
|
32105
|
+
return { value: 0, format: DEFAULT_DDB_FORMAT };
|
|
32106
|
+
}
|
|
32107
|
+
const deprecFactor = _factor / _life;
|
|
32108
|
+
if (deprecFactor > 1) {
|
|
32109
|
+
return { value: _period === 1 ? _cost - _salvage : 0, format: DEFAULT_DDB_FORMAT };
|
|
32110
|
+
}
|
|
32111
|
+
if (_period <= 1) {
|
|
32112
|
+
return {
|
|
32113
|
+
value: _cost * deprecFactor,
|
|
32114
|
+
format: DEFAULT_DDB_FORMAT,
|
|
32115
|
+
};
|
|
32116
|
+
}
|
|
32117
|
+
const previousCost = _cost * Math.pow(1 - deprecFactor, _period - 1);
|
|
32118
|
+
const nextCost = _cost * Math.pow(1 - deprecFactor, _period);
|
|
32119
|
+
const deprec = nextCost < _salvage ? previousCost - _salvage : previousCost - nextCost;
|
|
31663
32120
|
return {
|
|
31664
|
-
value:
|
|
31665
|
-
format:
|
|
32121
|
+
value: Math.max(deprec, 0),
|
|
32122
|
+
format: DEFAULT_DDB_FORMAT,
|
|
31666
32123
|
};
|
|
31667
32124
|
},
|
|
31668
32125
|
isExported: true,
|
|
@@ -31686,10 +32143,18 @@ const DISC = {
|
|
|
31686
32143
|
const _price = toNumber(price, this.locale);
|
|
31687
32144
|
const _redemption = toNumber(redemption, this.locale);
|
|
31688
32145
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
31692
|
-
|
|
32146
|
+
if (_settlement >= _maturity) {
|
|
32147
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
32148
|
+
}
|
|
32149
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
32150
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
32151
|
+
}
|
|
32152
|
+
if (_price <= 0) {
|
|
32153
|
+
return new EvaluationError(expectPriceStrictlyPositive(_price));
|
|
32154
|
+
}
|
|
32155
|
+
if (_redemption <= 0) {
|
|
32156
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
32157
|
+
}
|
|
31693
32158
|
/**
|
|
31694
32159
|
* https://support.microsoft.com/en-us/office/disc-function-71fce9f3-3f05-4acf-a5a3-eac6ef4daa53
|
|
31695
32160
|
*
|
|
@@ -31717,7 +32182,9 @@ const DOLLARDE = {
|
|
|
31717
32182
|
compute: function (fractionalPrice, unit) {
|
|
31718
32183
|
const price = toNumber(fractionalPrice, this.locale);
|
|
31719
32184
|
const _unit = Math.trunc(toNumber(unit, this.locale));
|
|
31720
|
-
|
|
32185
|
+
if (_unit <= 0) {
|
|
32186
|
+
return new EvaluationError(expectUnitStrictlyPositive(_unit));
|
|
32187
|
+
}
|
|
31721
32188
|
const truncatedPrice = Math.trunc(price);
|
|
31722
32189
|
const priceFractionalPart = price - truncatedPrice;
|
|
31723
32190
|
const frac = 10 ** Math.ceil(Math.log10(_unit)) / _unit;
|
|
@@ -31737,7 +32204,9 @@ const DOLLARFR = {
|
|
|
31737
32204
|
compute: function (decimalPrice, unit) {
|
|
31738
32205
|
const price = toNumber(decimalPrice, this.locale);
|
|
31739
32206
|
const _unit = Math.trunc(toNumber(unit, this.locale));
|
|
31740
|
-
|
|
32207
|
+
if (_unit <= 0) {
|
|
32208
|
+
return new EvaluationError(expectUnitStrictlyPositive(_unit));
|
|
32209
|
+
}
|
|
31741
32210
|
const truncatedPrice = Math.trunc(price);
|
|
31742
32211
|
const priceFractionalPart = price - truncatedPrice;
|
|
31743
32212
|
const frac = _unit / 10 ** Math.ceil(Math.log10(_unit));
|
|
@@ -31765,11 +32234,21 @@ const DURATION = {
|
|
|
31765
32234
|
const _yield = toNumber(securityYield, this.locale);
|
|
31766
32235
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
31767
32236
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31768
|
-
|
|
31769
|
-
|
|
31770
|
-
|
|
31771
|
-
|
|
31772
|
-
|
|
32237
|
+
if (start >= end) {
|
|
32238
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
32239
|
+
}
|
|
32240
|
+
if (isInvalidFrequency(_frequency)) {
|
|
32241
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
32242
|
+
}
|
|
32243
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
32244
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
32245
|
+
}
|
|
32246
|
+
if (_rate < 0) {
|
|
32247
|
+
return new EvaluationError(expectRatePositiveOrZero(_rate));
|
|
32248
|
+
}
|
|
32249
|
+
if (_yield < 0) {
|
|
32250
|
+
return new EvaluationError(expectYieldPositiveOrZero(_yield));
|
|
32251
|
+
}
|
|
31773
32252
|
const years = getYearFrac(start, end, _dayCountConvention);
|
|
31774
32253
|
const timeFirstYear = years - Math.trunc(years) || 1 / _frequency;
|
|
31775
32254
|
const nbrCoupons = Math.ceil(years * _frequency);
|
|
@@ -31801,8 +32280,12 @@ const EFFECT = {
|
|
|
31801
32280
|
compute: function (nominal_rate, periods_per_year) {
|
|
31802
32281
|
const nominal = toNumber(nominal_rate, this.locale);
|
|
31803
32282
|
const periods = Math.trunc(toNumber(periods_per_year, this.locale));
|
|
31804
|
-
|
|
31805
|
-
|
|
32283
|
+
if (nominal <= 0) {
|
|
32284
|
+
return new EvaluationError(expectNominalRateStrictlyPositive(nominal));
|
|
32285
|
+
}
|
|
32286
|
+
if (periods <= 0) {
|
|
32287
|
+
return new EvaluationError(expectPeriodsByYearStrictlyPositive(periods));
|
|
32288
|
+
}
|
|
31806
32289
|
// https://en.wikipedia.org/wiki/Nominal_interest_rate#Nominal_versus_effective_interest_rate
|
|
31807
32290
|
return Math.pow(1 + nominal / periods, periods) - 1;
|
|
31808
32291
|
},
|
|
@@ -31876,10 +32359,18 @@ const INTRATE = {
|
|
|
31876
32359
|
const _redemption = toNumber(redemption, this.locale);
|
|
31877
32360
|
const _investment = toNumber(investment, this.locale);
|
|
31878
32361
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
31879
|
-
|
|
31880
|
-
|
|
31881
|
-
|
|
31882
|
-
|
|
32362
|
+
if (_settlement >= _maturity) {
|
|
32363
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
32364
|
+
}
|
|
32365
|
+
if (_investment <= 0) {
|
|
32366
|
+
return new EvaluationError(expectInvestmentStrictlyPositive(_investment));
|
|
32367
|
+
}
|
|
32368
|
+
if (_redemption <= 0) {
|
|
32369
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
32370
|
+
}
|
|
32371
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
32372
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
32373
|
+
}
|
|
31883
32374
|
/**
|
|
31884
32375
|
* https://wiki.documentfoundation.org/Documentation/Calc_Functions/INTRATE
|
|
31885
32376
|
*
|
|
@@ -31934,7 +32425,9 @@ const IRR = {
|
|
|
31934
32425
|
],
|
|
31935
32426
|
compute: function (cashFlowAmounts, rateGuess = { value: DEFAULT_RATE_GUESS }) {
|
|
31936
32427
|
const _rateGuess = toNumber(rateGuess, this.locale);
|
|
31937
|
-
|
|
32428
|
+
if (_rateGuess <= -1) {
|
|
32429
|
+
return new EvaluationError(expectRateGuessStrictlyGreaterThanMinusOne(_rateGuess));
|
|
32430
|
+
}
|
|
31938
32431
|
// check that values contains at least one positive value and one negative value
|
|
31939
32432
|
// and extract number present in the cashFlowAmount argument
|
|
31940
32433
|
let positive = false;
|
|
@@ -31947,7 +32440,9 @@ const IRR = {
|
|
|
31947
32440
|
negative = true;
|
|
31948
32441
|
amounts.push(amount);
|
|
31949
32442
|
}, this.locale);
|
|
31950
|
-
|
|
32443
|
+
if (!positive || !negative) {
|
|
32444
|
+
return new EvaluationError(expectCashFlowsHavePositiveAndNegativesValues);
|
|
32445
|
+
}
|
|
31951
32446
|
const firstAmount = amounts.shift();
|
|
31952
32447
|
// The result of IRR is the rate at which the NPV() function will return zero with the given values.
|
|
31953
32448
|
// This algorithm uses the Newton's method on the NPV function to determine the result
|
|
@@ -31998,7 +32493,9 @@ const ISPMT = {
|
|
|
31998
32493
|
const period = toNumber(currentPeriod, this.locale);
|
|
31999
32494
|
const nOfPeriods = toNumber(numberOfPeriods, this.locale);
|
|
32000
32495
|
const investment = toNumber(presentValue, this.locale);
|
|
32001
|
-
|
|
32496
|
+
if (nOfPeriods === 0) {
|
|
32497
|
+
return new EvaluationError(expectNumberOfPeriodDifferentFromZero(nOfPeriods));
|
|
32498
|
+
}
|
|
32002
32499
|
const currentInvestment = investment - investment * (period / nOfPeriods);
|
|
32003
32500
|
return -1 * currentInvestment * interestRate;
|
|
32004
32501
|
},
|
|
@@ -32021,7 +32518,7 @@ const MDURATION = {
|
|
|
32021
32518
|
const duration = DURATION.compute.bind(this)(settlement, maturity, rate, securityYield, frequency, dayCountConvention);
|
|
32022
32519
|
const y = toNumber(securityYield, this.locale);
|
|
32023
32520
|
const k = Math.trunc(toNumber(frequency, this.locale));
|
|
32024
|
-
return duration / (1 + y / k);
|
|
32521
|
+
return toNumber(duration, this.locale) / (1 + y / k);
|
|
32025
32522
|
},
|
|
32026
32523
|
isExported: true,
|
|
32027
32524
|
};
|
|
@@ -32071,7 +32568,9 @@ const MIRR = {
|
|
|
32071
32568
|
pv += amount / (fRate + 1) ** i;
|
|
32072
32569
|
}
|
|
32073
32570
|
}
|
|
32074
|
-
|
|
32571
|
+
if (pv === 0 || fv === 0) {
|
|
32572
|
+
return new EvaluationError(expectCashFlowsHavePositiveAndNegativesValues);
|
|
32573
|
+
}
|
|
32075
32574
|
const exponent = 1 / (n - 1);
|
|
32076
32575
|
return (-fv / pv) ** exponent - 1;
|
|
32077
32576
|
},
|
|
@@ -32089,8 +32588,12 @@ const NOMINAL = {
|
|
|
32089
32588
|
compute: function (effective_rate, periods_per_year) {
|
|
32090
32589
|
const effective = toNumber(effective_rate, this.locale);
|
|
32091
32590
|
const periods = Math.trunc(toNumber(periods_per_year, this.locale));
|
|
32092
|
-
|
|
32093
|
-
|
|
32591
|
+
if (effective <= 0) {
|
|
32592
|
+
return new EvaluationError(expectEffectiveRateStrictlyPositive(effective));
|
|
32593
|
+
}
|
|
32594
|
+
if (periods <= 0) {
|
|
32595
|
+
return new EvaluationError(expectPeriodsByYearStrictlyPositive(periods));
|
|
32596
|
+
}
|
|
32094
32597
|
// https://en.wikipedia.org/wiki/Nominal_interest_rate#Nominal_versus_effective_interest_rate
|
|
32095
32598
|
return (Math.pow(effective + 1, 1 / periods) - 1) * periods;
|
|
32096
32599
|
},
|
|
@@ -32158,7 +32661,9 @@ const NPV = {
|
|
|
32158
32661
|
// to do: replace by dollar format
|
|
32159
32662
|
compute: function (discount, ...values) {
|
|
32160
32663
|
const _discount = toNumber(discount, this.locale);
|
|
32161
|
-
|
|
32664
|
+
if (_discount === -1) {
|
|
32665
|
+
return new EvaluationError(expectDiscountDifferentFromMinusOne(_discount));
|
|
32666
|
+
}
|
|
32162
32667
|
return {
|
|
32163
32668
|
value: npvResult(_discount, 0, values, this.locale),
|
|
32164
32669
|
format: "#,##0.00",
|
|
@@ -32180,9 +32685,15 @@ const PDURATION = {
|
|
|
32180
32685
|
const _rate = toNumber(rate, this.locale);
|
|
32181
32686
|
const _presentValue = toNumber(presentValue, this.locale);
|
|
32182
32687
|
const _futureValue = toNumber(futureValue, this.locale);
|
|
32183
|
-
|
|
32184
|
-
|
|
32185
|
-
|
|
32688
|
+
if (_rate <= 0) {
|
|
32689
|
+
return new EvaluationError(expectRateStrictlyPositive(_rate));
|
|
32690
|
+
}
|
|
32691
|
+
if (_presentValue <= 0) {
|
|
32692
|
+
return new EvaluationError(expectPresentValueStrictlyPositive(_presentValue));
|
|
32693
|
+
}
|
|
32694
|
+
if (_futureValue <= 0) {
|
|
32695
|
+
return new EvaluationError(expectFutureValueStrictlyPositive(_futureValue));
|
|
32696
|
+
}
|
|
32186
32697
|
return (Math.log(_futureValue) - Math.log(_presentValue)) / Math.log(1 + _rate);
|
|
32187
32698
|
},
|
|
32188
32699
|
isExported: true,
|
|
@@ -32191,7 +32702,9 @@ const PDURATION = {
|
|
|
32191
32702
|
// PMT
|
|
32192
32703
|
// -----------------------------------------------------------------------------
|
|
32193
32704
|
function pmt(r, n, pv, fv, t) {
|
|
32194
|
-
|
|
32705
|
+
if (n <= 0) {
|
|
32706
|
+
throw new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
32707
|
+
}
|
|
32195
32708
|
/**
|
|
32196
32709
|
* https://wiki.documentfoundation.org/Documentation/Calc_Functions/PMT
|
|
32197
32710
|
*
|
|
@@ -32229,8 +32742,12 @@ const PMT = {
|
|
|
32229
32742
|
// PPMT
|
|
32230
32743
|
// -----------------------------------------------------------------------------
|
|
32231
32744
|
function ppmt(r, per, n, pValue, fValue, t) {
|
|
32232
|
-
|
|
32233
|
-
|
|
32745
|
+
if (n <= 0) {
|
|
32746
|
+
throw new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
32747
|
+
}
|
|
32748
|
+
if (per <= 0 || per > n) {
|
|
32749
|
+
throw new EvaluationError(expectPeriodBetweenOneAndNumberOfPeriods(n));
|
|
32750
|
+
}
|
|
32234
32751
|
const payment = pmt(r, n, pValue, fValue, t);
|
|
32235
32752
|
if (t === 1 && per === 1)
|
|
32236
32753
|
return payment;
|
|
@@ -32318,12 +32835,24 @@ const PRICE = {
|
|
|
32318
32835
|
const _redemption = toNumber(redemption, this.locale);
|
|
32319
32836
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
32320
32837
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
|
|
32324
|
-
|
|
32325
|
-
|
|
32326
|
-
|
|
32838
|
+
if (_settlement >= _maturity) {
|
|
32839
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
32840
|
+
}
|
|
32841
|
+
if (isInvalidFrequency(_frequency)) {
|
|
32842
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
32843
|
+
}
|
|
32844
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
32845
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
32846
|
+
}
|
|
32847
|
+
if (_rate < 0) {
|
|
32848
|
+
return new EvaluationError(expectRatePositiveOrZero(_rate));
|
|
32849
|
+
}
|
|
32850
|
+
if (_yield < 0) {
|
|
32851
|
+
return new EvaluationError(expectYieldPositiveOrZero(_yield));
|
|
32852
|
+
}
|
|
32853
|
+
if (_redemption <= 0) {
|
|
32854
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
32855
|
+
}
|
|
32327
32856
|
const years = getYearFrac(_settlement, _maturity, _dayCountConvention);
|
|
32328
32857
|
const nbrRealCoupons = years * _frequency;
|
|
32329
32858
|
const nbrFullCoupons = Math.ceil(nbrRealCoupons);
|
|
@@ -32363,10 +32892,18 @@ const PRICEDISC = {
|
|
|
32363
32892
|
const _discount = toNumber(discount, this.locale);
|
|
32364
32893
|
const _redemption = toNumber(redemption, this.locale);
|
|
32365
32894
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
32366
|
-
|
|
32367
|
-
|
|
32368
|
-
|
|
32369
|
-
|
|
32895
|
+
if (_settlement >= _maturity) {
|
|
32896
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
32897
|
+
}
|
|
32898
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
32899
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
32900
|
+
}
|
|
32901
|
+
if (_discount <= 0) {
|
|
32902
|
+
return new EvaluationError(expectDiscountStrictlyPositive(_discount));
|
|
32903
|
+
}
|
|
32904
|
+
if (_redemption <= 0) {
|
|
32905
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
32906
|
+
}
|
|
32370
32907
|
/**
|
|
32371
32908
|
* https://support.microsoft.com/en-us/office/pricedisc-function-d06ad7c1-380e-4be7-9fd9-75e3079acfd3
|
|
32372
32909
|
*
|
|
@@ -32401,11 +32938,21 @@ const PRICEMAT = {
|
|
|
32401
32938
|
const _rate = toNumber(rate, this.locale);
|
|
32402
32939
|
const _yield = toNumber(securityYield, this.locale);
|
|
32403
32940
|
const _dayCount = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
32404
|
-
|
|
32405
|
-
|
|
32406
|
-
|
|
32407
|
-
|
|
32408
|
-
|
|
32941
|
+
if (_settlement <= _issue) {
|
|
32942
|
+
return new EvaluationError(expectSettlementStrictlyGreaterThanIssue(_settlement, _issue));
|
|
32943
|
+
}
|
|
32944
|
+
if (_settlement >= _maturity) {
|
|
32945
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
32946
|
+
}
|
|
32947
|
+
if (isInvalidDayCountConvention(_dayCount)) {
|
|
32948
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCount));
|
|
32949
|
+
}
|
|
32950
|
+
if (_rate < 0) {
|
|
32951
|
+
return new EvaluationError(expectRatePositiveOrZero(_rate));
|
|
32952
|
+
}
|
|
32953
|
+
if (_yield < 0) {
|
|
32954
|
+
return new EvaluationError(expectYieldPositiveOrZero(_yield));
|
|
32955
|
+
}
|
|
32409
32956
|
/**
|
|
32410
32957
|
* https://support.microsoft.com/en-us/office/pricemat-function-52c3b4da-bc7e-476a-989f-a95f675cae77
|
|
32411
32958
|
*
|
|
@@ -32463,9 +33010,15 @@ const RATE = {
|
|
|
32463
33010
|
const guess = toNumber(rateGuess, this.locale) || RATE_GUESS_DEFAULT;
|
|
32464
33011
|
let fv = toNumber(futureValue, this.locale);
|
|
32465
33012
|
let pv = toNumber(presentValue, this.locale);
|
|
32466
|
-
|
|
32467
|
-
|
|
32468
|
-
|
|
33013
|
+
if (n <= 0) {
|
|
33014
|
+
return new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
33015
|
+
}
|
|
33016
|
+
if (!havePositiveAndNegativeValues([payment, pv, fv])) {
|
|
33017
|
+
return new EvaluationError(_t("There must be both positive and negative values in [payment_amount, present_value, future_value]."));
|
|
33018
|
+
}
|
|
33019
|
+
if (guess <= -1) {
|
|
33020
|
+
return new EvaluationError(expectRateGuessStrictlyGreaterThanMinusOne(guess));
|
|
33021
|
+
}
|
|
32469
33022
|
fv -= payment * type;
|
|
32470
33023
|
pv += payment * type;
|
|
32471
33024
|
// https://github.com/apache/openoffice/blob/trunk/main/sc/source/core/tool/interpr2.cxx
|
|
@@ -32508,10 +33061,18 @@ const RECEIVED = {
|
|
|
32508
33061
|
const _investment = toNumber(investment, this.locale);
|
|
32509
33062
|
const _discount = toNumber(discount, this.locale);
|
|
32510
33063
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
33064
|
+
if (_settlement >= _maturity) {
|
|
33065
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
33066
|
+
}
|
|
33067
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
33068
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
33069
|
+
}
|
|
33070
|
+
if (_investment <= 0) {
|
|
33071
|
+
return new EvaluationError(expectInvestmentStrictlyPositive(_investment));
|
|
33072
|
+
}
|
|
33073
|
+
if (_discount <= 0) {
|
|
33074
|
+
return new EvaluationError(expectDiscountStrictlyPositive(_discount));
|
|
33075
|
+
}
|
|
32515
33076
|
/**
|
|
32516
33077
|
* https://support.microsoft.com/en-us/office/received-function-7a3f8b93-6611-4f81-8576-828312c9b5e5
|
|
32517
33078
|
*
|
|
@@ -32542,7 +33103,9 @@ const RRI = {
|
|
|
32542
33103
|
const n = toNumber(numberOfPeriods, this.locale);
|
|
32543
33104
|
const pv = toNumber(presentValue, this.locale);
|
|
32544
33105
|
const fv = toNumber(futureValue, this.locale);
|
|
32545
|
-
|
|
33106
|
+
if (n <= 0) {
|
|
33107
|
+
return new EvaluationError(expectNumberOfPeriodsStrictlyPositive(n));
|
|
33108
|
+
}
|
|
32546
33109
|
/**
|
|
32547
33110
|
* https://support.microsoft.com/en-us/office/rri-function-6f5822d8-7ef1-4233-944c-79e8172930f4
|
|
32548
33111
|
*
|
|
@@ -32591,9 +33154,15 @@ const SYD = {
|
|
|
32591
33154
|
const _salvage = toNumber(salvage, this.locale);
|
|
32592
33155
|
const _life = toNumber(life, this.locale);
|
|
32593
33156
|
const _period = toNumber(period, this.locale);
|
|
32594
|
-
|
|
32595
|
-
|
|
32596
|
-
|
|
33157
|
+
if (_period <= 0) {
|
|
33158
|
+
return new EvaluationError(expectPeriodStrictlyPositive(_period));
|
|
33159
|
+
}
|
|
33160
|
+
if (_life <= 0) {
|
|
33161
|
+
return new EvaluationError(expectLifeStrictlyPositive(_life));
|
|
33162
|
+
}
|
|
33163
|
+
if (_period > _life) {
|
|
33164
|
+
return new EvaluationError(expectPeriodSmallerOrEqualToLife(_period, _life));
|
|
33165
|
+
}
|
|
32597
33166
|
/**
|
|
32598
33167
|
* This deprecation method use the sum of digits of the periods of the life as the deprecation factor.
|
|
32599
33168
|
* For example for a life = 5, we have a deprecation factor or 1 + 2 + 3 + 4 + 5 = 15 = life * (life + 1) / 2 = F.
|
|
@@ -32638,10 +33207,18 @@ const TBILLPRICE = {
|
|
|
32638
33207
|
const start = Math.trunc(toNumber(settlement, this.locale));
|
|
32639
33208
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
32640
33209
|
const disc = toNumber(discount, this.locale);
|
|
32641
|
-
|
|
32642
|
-
|
|
32643
|
-
|
|
32644
|
-
|
|
33210
|
+
if (start >= end) {
|
|
33211
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
33212
|
+
}
|
|
33213
|
+
if (!isSettlementLessThanOneYearBeforeMaturity(start, end, this.locale)) {
|
|
33214
|
+
return new EvaluationError(expectSettlementLessThanOneYearBeforeMaturity(start, end));
|
|
33215
|
+
}
|
|
33216
|
+
if (disc <= 0) {
|
|
33217
|
+
return new EvaluationError(expectDiscountStrictlyPositive(disc));
|
|
33218
|
+
}
|
|
33219
|
+
if (disc >= 1) {
|
|
33220
|
+
return new EvaluationError(expectDiscountStrictlySmallerThanOne(disc));
|
|
33221
|
+
}
|
|
32645
33222
|
return tBillPrice(start, end, disc);
|
|
32646
33223
|
},
|
|
32647
33224
|
isExported: true,
|
|
@@ -32660,10 +33237,18 @@ const TBILLEQ = {
|
|
|
32660
33237
|
const start = Math.trunc(toNumber(settlement, this.locale));
|
|
32661
33238
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
32662
33239
|
const disc = toNumber(discount, this.locale);
|
|
32663
|
-
|
|
32664
|
-
|
|
32665
|
-
|
|
32666
|
-
|
|
33240
|
+
if (start >= end) {
|
|
33241
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
33242
|
+
}
|
|
33243
|
+
if (!isSettlementLessThanOneYearBeforeMaturity(start, end, this.locale)) {
|
|
33244
|
+
return new EvaluationError(expectSettlementLessThanOneYearBeforeMaturity(start, end));
|
|
33245
|
+
}
|
|
33246
|
+
if (disc <= 0) {
|
|
33247
|
+
return new EvaluationError(expectDiscountStrictlyPositive(disc));
|
|
33248
|
+
}
|
|
33249
|
+
if (disc >= 1) {
|
|
33250
|
+
return new EvaluationError(expectDiscountStrictlySmallerThanOne(disc));
|
|
33251
|
+
}
|
|
32667
33252
|
/**
|
|
32668
33253
|
* https://support.microsoft.com/en-us/office/tbilleq-function-2ab72d90-9b4d-4efe-9fc2-0f81f2c19c8c
|
|
32669
33254
|
*
|
|
@@ -32717,9 +33302,15 @@ const TBILLYIELD = {
|
|
|
32717
33302
|
const start = Math.trunc(toNumber(settlement, this.locale));
|
|
32718
33303
|
const end = Math.trunc(toNumber(maturity, this.locale));
|
|
32719
33304
|
const p = toNumber(price, this.locale);
|
|
32720
|
-
|
|
32721
|
-
|
|
32722
|
-
|
|
33305
|
+
if (start >= end) {
|
|
33306
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(start, end));
|
|
33307
|
+
}
|
|
33308
|
+
if (!isSettlementLessThanOneYearBeforeMaturity(start, end, this.locale)) {
|
|
33309
|
+
return new EvaluationError(expectSettlementLessThanOneYearBeforeMaturity(start, end));
|
|
33310
|
+
}
|
|
33311
|
+
if (p <= 0) {
|
|
33312
|
+
return new EvaluationError(expectPriceStrictlyPositive(p));
|
|
33313
|
+
}
|
|
32723
33314
|
/**
|
|
32724
33315
|
* https://support.microsoft.com/en-us/office/tbillyield-function-6d381232-f4b0-4cd5-8e97-45b9c03468ba
|
|
32725
33316
|
*
|
|
@@ -32765,10 +33356,30 @@ const VDB = {
|
|
|
32765
33356
|
const _endPeriod = Math.trunc(toNumber(endPeriod, this.locale));
|
|
32766
33357
|
const _factor = toNumber(factor, this.locale);
|
|
32767
33358
|
const _noSwitch = toBoolean(noSwitch);
|
|
32768
|
-
|
|
32769
|
-
|
|
32770
|
-
|
|
32771
|
-
|
|
33359
|
+
if (_cost < 0) {
|
|
33360
|
+
return new EvaluationError(expectCostPositiveOrZero(_cost));
|
|
33361
|
+
}
|
|
33362
|
+
if (_salvage < 0) {
|
|
33363
|
+
return new EvaluationError(expectSalvagePositiveOrZero(_salvage));
|
|
33364
|
+
}
|
|
33365
|
+
if (_life <= 0) {
|
|
33366
|
+
return new EvaluationError(expectLifeStrictlyPositive(_life));
|
|
33367
|
+
}
|
|
33368
|
+
if (_startPeriod < 0) {
|
|
33369
|
+
return new EvaluationError(expectStartPeriodPositiveOrZero(_startPeriod));
|
|
33370
|
+
}
|
|
33371
|
+
if (_endPeriod < 0) {
|
|
33372
|
+
return new EvaluationError(expectEndPeriodPositiveOrZero(_endPeriod));
|
|
33373
|
+
}
|
|
33374
|
+
if (_startPeriod > _endPeriod) {
|
|
33375
|
+
return new EvaluationError(expectStartPeriodSmallerOrEqualEndPeriod(_startPeriod, _endPeriod));
|
|
33376
|
+
}
|
|
33377
|
+
if (_endPeriod > _life) {
|
|
33378
|
+
return new EvaluationError(expectEndPeriodSmallerOrEqualToLife(_endPeriod, _life));
|
|
33379
|
+
}
|
|
33380
|
+
if (_factor <= 0) {
|
|
33381
|
+
return new EvaluationError(expectDeprecationFactorStrictlyPositive(_factor));
|
|
33382
|
+
}
|
|
32772
33383
|
if (_cost === 0)
|
|
32773
33384
|
return 0;
|
|
32774
33385
|
if (_salvage >= _cost) {
|
|
@@ -32818,12 +33429,20 @@ const XIRR = {
|
|
|
32818
33429
|
],
|
|
32819
33430
|
compute: function (cashflowAmounts, cashflowDates, rateGuess = { value: RATE_GUESS_DEFAULT }) {
|
|
32820
33431
|
const guess = toNumber(rateGuess, this.locale);
|
|
33432
|
+
if (!areSameDimensions(cashflowAmounts, cashflowDates)) {
|
|
33433
|
+
return new EvaluationError(expectCashFlowsAndDatesHaveSameDimension);
|
|
33434
|
+
}
|
|
32821
33435
|
const _cashFlows = cashflowAmounts.flat().map((val) => toNumber(val, this.locale));
|
|
32822
33436
|
const _dates = cashflowDates.flat().map((val) => toNumber(val, this.locale));
|
|
32823
|
-
|
|
32824
|
-
|
|
32825
|
-
|
|
32826
|
-
|
|
33437
|
+
if (!havePositiveAndNegativeValues(_cashFlows)) {
|
|
33438
|
+
return new EvaluationError(expectCashFlowsHavePositiveAndNegativesValues);
|
|
33439
|
+
}
|
|
33440
|
+
if (_dates.some((date) => date < _dates[0])) {
|
|
33441
|
+
return new EvaluationError(expectEveryDateGreaterThanFirstDateOfCashFlowDates(_dates[0]));
|
|
33442
|
+
}
|
|
33443
|
+
if (guess <= -1) {
|
|
33444
|
+
return new EvaluationError(expectRateGuessStrictlyGreaterThanMinusOne(guess));
|
|
33445
|
+
}
|
|
32827
33446
|
const map = new Map();
|
|
32828
33447
|
for (const i of range(0, _dates.length)) {
|
|
32829
33448
|
const date = _dates[i];
|
|
@@ -32888,20 +33507,21 @@ const XNPV = {
|
|
|
32888
33507
|
],
|
|
32889
33508
|
compute: function (discount, cashflowAmounts, cashflowDates) {
|
|
32890
33509
|
const rate = toNumber(discount, this.locale);
|
|
32891
|
-
|
|
32892
|
-
|
|
32893
|
-
: [strictToNumber(cashflowAmounts, this.locale)];
|
|
32894
|
-
const _dates = isMatrix(cashflowDates)
|
|
32895
|
-
? cashflowDates.flat().map((data) => strictToNumber(data, this.locale))
|
|
32896
|
-
: [strictToNumber(cashflowDates, this.locale)];
|
|
32897
|
-
if (isMatrix(cashflowDates) && isMatrix(cashflowAmounts)) {
|
|
32898
|
-
assertCashFlowsAndDatesHaveSameDimension(cashflowAmounts, cashflowDates);
|
|
33510
|
+
if (!areSameDimensions(cashflowAmounts, cashflowDates)) {
|
|
33511
|
+
return new EvaluationError(expectCashFlowsAndDatesHaveSameDimension);
|
|
32899
33512
|
}
|
|
32900
|
-
|
|
32901
|
-
|
|
33513
|
+
const _cashFlows = toMatrix(cashflowAmounts)
|
|
33514
|
+
.flat()
|
|
33515
|
+
.map((val) => strictToNumber(val, this.locale));
|
|
33516
|
+
const _dates = toMatrix(cashflowDates)
|
|
33517
|
+
.flat()
|
|
33518
|
+
.map((val) => strictToNumber(val, this.locale));
|
|
33519
|
+
if (_dates.some((date) => date < _dates[0])) {
|
|
33520
|
+
return new EvaluationError(expectEveryDateGreaterThanFirstDateOfCashFlowDates(_dates[0]));
|
|
33521
|
+
}
|
|
33522
|
+
if (rate <= 0) {
|
|
33523
|
+
return new EvaluationError(expectRateStrictlyPositive(rate));
|
|
32902
33524
|
}
|
|
32903
|
-
assertEveryDateGreaterThanFirstDateOfCashFlowDates(_dates);
|
|
32904
|
-
assertRateStrictlyPositive(rate);
|
|
32905
33525
|
if (_cashFlows.length === 1)
|
|
32906
33526
|
return _cashFlows[0];
|
|
32907
33527
|
// aggregate values of the same date
|
|
@@ -32961,12 +33581,24 @@ const YIELD = {
|
|
|
32961
33581
|
const _redemption = toNumber(redemption, this.locale);
|
|
32962
33582
|
const _frequency = Math.trunc(toNumber(frequency, this.locale));
|
|
32963
33583
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
32964
|
-
|
|
32965
|
-
|
|
32966
|
-
|
|
32967
|
-
|
|
32968
|
-
|
|
32969
|
-
|
|
33584
|
+
if (_settlement >= _maturity) {
|
|
33585
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
33586
|
+
}
|
|
33587
|
+
if (isInvalidFrequency(_frequency)) {
|
|
33588
|
+
return new EvaluationError(expectCouponFrequencyIsValid(_frequency));
|
|
33589
|
+
}
|
|
33590
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
33591
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
33592
|
+
}
|
|
33593
|
+
if (_rate < 0) {
|
|
33594
|
+
return new EvaluationError(expectRatePositiveOrZero(_rate));
|
|
33595
|
+
}
|
|
33596
|
+
if (_price <= 0) {
|
|
33597
|
+
return new EvaluationError(expectPriceStrictlyPositive(_price));
|
|
33598
|
+
}
|
|
33599
|
+
if (_redemption <= 0) {
|
|
33600
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
33601
|
+
}
|
|
32970
33602
|
const years = getYearFrac(_settlement, _maturity, _dayCountConvention);
|
|
32971
33603
|
const nbrRealCoupons = years * _frequency;
|
|
32972
33604
|
const nbrFullCoupons = Math.ceil(nbrRealCoupons);
|
|
@@ -33033,10 +33665,18 @@ const YIELDDISC = {
|
|
|
33033
33665
|
const _price = toNumber(price, this.locale);
|
|
33034
33666
|
const _redemption = toNumber(redemption, this.locale);
|
|
33035
33667
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
33036
|
-
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33668
|
+
if (_settlement >= _maturity) {
|
|
33669
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
33670
|
+
}
|
|
33671
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
33672
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
33673
|
+
}
|
|
33674
|
+
if (_price <= 0) {
|
|
33675
|
+
return new EvaluationError(expectPriceStrictlyPositive(_price));
|
|
33676
|
+
}
|
|
33677
|
+
if (_redemption <= 0) {
|
|
33678
|
+
return new EvaluationError(expectRedemptionStrictlyPositive(_redemption));
|
|
33679
|
+
}
|
|
33040
33680
|
/**
|
|
33041
33681
|
* https://wiki.documentfoundation.org/Documentation/Calc_Functions/YIELDDISC
|
|
33042
33682
|
*
|
|
@@ -33070,11 +33710,21 @@ const YIELDMAT = {
|
|
|
33070
33710
|
const _rate = toNumber(rate, this.locale);
|
|
33071
33711
|
const _price = toNumber(price, this.locale);
|
|
33072
33712
|
const _dayCountConvention = Math.trunc(toNumber(dayCountConvention, this.locale));
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
|
|
33077
|
-
|
|
33713
|
+
if (_settlement >= _maturity) {
|
|
33714
|
+
return new EvaluationError(expectMaturityStrictlyGreaterThanSettlement(_settlement, _maturity));
|
|
33715
|
+
}
|
|
33716
|
+
if (isInvalidDayCountConvention(_dayCountConvention)) {
|
|
33717
|
+
return new EvaluationError(expectDayCountConventionIsValid(_dayCountConvention));
|
|
33718
|
+
}
|
|
33719
|
+
if (_settlement < _issue) {
|
|
33720
|
+
return new EvaluationError(expectSettlementGreaterOrEqualToIssue(_settlement, _issue));
|
|
33721
|
+
}
|
|
33722
|
+
if (_rate < 0) {
|
|
33723
|
+
return new EvaluationError(expectRatePositiveOrZero(_rate));
|
|
33724
|
+
}
|
|
33725
|
+
if (_price <= 0) {
|
|
33726
|
+
return new EvaluationError(expectPriceStrictlyPositive(_price));
|
|
33727
|
+
}
|
|
33078
33728
|
const issueToMaturity = getYearFrac(_issue, _maturity, _dayCountConvention);
|
|
33079
33729
|
const issueToSettlement = getYearFrac(_issue, _settlement, _dayCountConvention);
|
|
33080
33730
|
const settlementToMaturity = getYearFrac(_settlement, _maturity, _dayCountConvention);
|
|
@@ -33808,7 +34458,9 @@ const CELL = {
|
|
|
33808
34458
|
],
|
|
33809
34459
|
compute: function (info, reference) {
|
|
33810
34460
|
const _info = toString(info).toLowerCase();
|
|
33811
|
-
|
|
34461
|
+
if (!CELL_INFO_TYPES.includes(_info)) {
|
|
34462
|
+
return new EvaluationError(_t("The info_type should be one of %s.", CELL_INFO_TYPES.join(", ")));
|
|
34463
|
+
}
|
|
33812
34464
|
const sheetId = this.__originSheetId;
|
|
33813
34465
|
const _reference = toString(reference);
|
|
33814
34466
|
const topLeftReference = _reference.includes(":") ? _reference.split(":")[0] : _reference;
|
|
@@ -33977,7 +34629,9 @@ const AND = {
|
|
|
33977
34629
|
],
|
|
33978
34630
|
compute: function (...logicalExpressions) {
|
|
33979
34631
|
const { result, foundBoolean } = boolAnd(logicalExpressions);
|
|
33980
|
-
|
|
34632
|
+
if (!foundBoolean) {
|
|
34633
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] has no valid input data."));
|
|
34634
|
+
}
|
|
33981
34635
|
return result;
|
|
33982
34636
|
},
|
|
33983
34637
|
isExported: true,
|
|
@@ -34009,7 +34663,7 @@ const IF = {
|
|
|
34009
34663
|
return { value: "" };
|
|
34010
34664
|
}
|
|
34011
34665
|
if (result.value === null) {
|
|
34012
|
-
result
|
|
34666
|
+
return { ...result, value: "" };
|
|
34013
34667
|
}
|
|
34014
34668
|
return result;
|
|
34015
34669
|
},
|
|
@@ -34030,7 +34684,7 @@ const IFERROR = {
|
|
|
34030
34684
|
return { value: "" };
|
|
34031
34685
|
}
|
|
34032
34686
|
if (result.value === null) {
|
|
34033
|
-
result
|
|
34687
|
+
return { ...result, value: "" };
|
|
34034
34688
|
}
|
|
34035
34689
|
return result;
|
|
34036
34690
|
},
|
|
@@ -34051,7 +34705,7 @@ const IFNA = {
|
|
|
34051
34705
|
return { value: "" };
|
|
34052
34706
|
}
|
|
34053
34707
|
if (result.value === null) {
|
|
34054
|
-
result
|
|
34708
|
+
return { ...result, value: "" };
|
|
34055
34709
|
}
|
|
34056
34710
|
return result;
|
|
34057
34711
|
},
|
|
@@ -34069,7 +34723,9 @@ const IFS = {
|
|
|
34069
34723
|
arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
|
|
34070
34724
|
],
|
|
34071
34725
|
compute: function (...values) {
|
|
34072
|
-
|
|
34726
|
+
if (values.length % 2 !== 0) {
|
|
34727
|
+
return new EvaluationError(_t("Wrong number of arguments. Expected an even number of arguments."));
|
|
34728
|
+
}
|
|
34073
34729
|
for (let n = 0; n < values.length - 1; n += 2) {
|
|
34074
34730
|
if (toBoolean(values[n]?.value)) {
|
|
34075
34731
|
const result = values[n + 1];
|
|
@@ -34077,7 +34733,7 @@ const IFS = {
|
|
|
34077
34733
|
return { value: "" };
|
|
34078
34734
|
}
|
|
34079
34735
|
if (result.value === null) {
|
|
34080
|
-
result
|
|
34736
|
+
return { ...result, value: "" };
|
|
34081
34737
|
}
|
|
34082
34738
|
return result;
|
|
34083
34739
|
}
|
|
@@ -34110,7 +34766,9 @@ const OR = {
|
|
|
34110
34766
|
],
|
|
34111
34767
|
compute: function (...logicalExpressions) {
|
|
34112
34768
|
const { result, foundBoolean } = boolOr(logicalExpressions);
|
|
34113
|
-
|
|
34769
|
+
if (!foundBoolean) {
|
|
34770
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] has no valid input data."));
|
|
34771
|
+
}
|
|
34114
34772
|
return result;
|
|
34115
34773
|
},
|
|
34116
34774
|
isExported: true,
|
|
@@ -34171,7 +34829,9 @@ const XOR = {
|
|
|
34171
34829
|
acc = acc ? !arg : arg;
|
|
34172
34830
|
return true; // no stop condition
|
|
34173
34831
|
});
|
|
34174
|
-
|
|
34832
|
+
if (!foundBoolean) {
|
|
34833
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] has no valid input data."));
|
|
34834
|
+
}
|
|
34175
34835
|
return acc;
|
|
34176
34836
|
},
|
|
34177
34837
|
isExported: true,
|
|
@@ -34224,6 +34884,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
|
|
|
34224
34884
|
if (range === undefined || range.invalidXc || range.invalidSheetName) {
|
|
34225
34885
|
throw new InvalidReferenceError();
|
|
34226
34886
|
}
|
|
34887
|
+
if (evalContext.__originCellPosition &&
|
|
34888
|
+
range.sheetId === evalContext.__originSheetId &&
|
|
34889
|
+
isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
|
|
34890
|
+
throw new CircularDependencyError();
|
|
34891
|
+
}
|
|
34227
34892
|
dependencies.push(range);
|
|
34228
34893
|
}
|
|
34229
34894
|
for (const measure of forMeasures) {
|
|
@@ -34261,10 +34926,16 @@ const ADDRESS = {
|
|
|
34261
34926
|
compute: function (row, column, absoluteRelativeMode = { value: DEFAULT_ABSOLUTE_RELATIVE_MODE }, useA1Notation = { value: true }, sheet) {
|
|
34262
34927
|
const rowNumber = strictToInteger(row, this.locale);
|
|
34263
34928
|
const colNumber = strictToInteger(column, this.locale);
|
|
34264
|
-
|
|
34265
|
-
|
|
34929
|
+
if (rowNumber < 1) {
|
|
34930
|
+
return new EvaluationError(expectNumberGreaterThanOrEqualToOne(rowNumber));
|
|
34931
|
+
}
|
|
34932
|
+
if (colNumber < 1) {
|
|
34933
|
+
return new EvaluationError(expectNumberGreaterThanOrEqualToOne(colNumber));
|
|
34934
|
+
}
|
|
34266
34935
|
const _absoluteRelativeMode = strictToInteger(absoluteRelativeMode, this.locale);
|
|
34267
|
-
|
|
34936
|
+
if (![1, 2, 3, 4].includes(_absoluteRelativeMode)) {
|
|
34937
|
+
return new EvaluationError(expectNumberRangeError(1, 4, _absoluteRelativeMode));
|
|
34938
|
+
}
|
|
34268
34939
|
const _useA1Notation = toBoolean(useA1Notation);
|
|
34269
34940
|
let cellReference;
|
|
34270
34941
|
if (_useA1Notation) {
|
|
@@ -34301,7 +34972,9 @@ const COLUMN = {
|
|
|
34301
34972
|
const column = cellReference === undefined
|
|
34302
34973
|
? this.__originCellPosition?.col
|
|
34303
34974
|
: toZone(cellReference.value).left;
|
|
34304
|
-
|
|
34975
|
+
if (column === undefined) {
|
|
34976
|
+
return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
|
|
34977
|
+
}
|
|
34305
34978
|
return column + 1;
|
|
34306
34979
|
},
|
|
34307
34980
|
isExported: true,
|
|
@@ -34335,7 +35008,9 @@ const HLOOKUP = {
|
|
|
34335
35008
|
compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
|
|
34336
35009
|
const _index = Math.trunc(toNumber(index?.value, this.locale));
|
|
34337
35010
|
const _range = toMatrix(range);
|
|
34338
|
-
|
|
35011
|
+
if (1 > _index || _index > _range[0].length) {
|
|
35012
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
|
|
35013
|
+
}
|
|
34339
35014
|
const getValueFromRange = (range, index) => range[index][0].value;
|
|
34340
35015
|
const _isSorted = toBoolean(isSorted.value);
|
|
34341
35016
|
const colIndex = _isSorted
|
|
@@ -34363,10 +35038,12 @@ const INDEX = {
|
|
|
34363
35038
|
const _reference = toMatrix(reference);
|
|
34364
35039
|
const _row = toNumber(row.value, this.locale);
|
|
34365
35040
|
const _column = toNumber(column.value, this.locale);
|
|
34366
|
-
|
|
34367
|
-
_column - 1
|
|
34368
|
-
_row
|
|
34369
|
-
_row - 1
|
|
35041
|
+
if (_column < 0 ||
|
|
35042
|
+
_column - 1 >= _reference.length ||
|
|
35043
|
+
_row < 0 ||
|
|
35044
|
+
_row - 1 >= _reference[0].length) {
|
|
35045
|
+
return new EvaluationError(_t("Index out of range."));
|
|
35046
|
+
}
|
|
34370
35047
|
if (_row === 0 && _column === 0) {
|
|
34371
35048
|
return _reference;
|
|
34372
35049
|
}
|
|
@@ -34457,12 +35134,18 @@ const LOOKUP = {
|
|
|
34457
35134
|
}
|
|
34458
35135
|
nbCol = _resultRange.length;
|
|
34459
35136
|
nbRow = _resultRange[0].length;
|
|
34460
|
-
|
|
35137
|
+
if (nbCol !== 1 && nbRow !== 1) {
|
|
35138
|
+
return new EvaluationError(_t("The result_range must be a single row or a single column."));
|
|
35139
|
+
}
|
|
34461
35140
|
if (nbCol > 1) {
|
|
34462
|
-
|
|
35141
|
+
if (index > nbCol - 1) {
|
|
35142
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] evaluates to an out of range row value %s.", index + 1));
|
|
35143
|
+
}
|
|
34463
35144
|
return _resultRange[index][0];
|
|
34464
35145
|
}
|
|
34465
|
-
|
|
35146
|
+
if (index > nbRow - 1) {
|
|
35147
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] evaluates to an out of range column value %s.", index + 1));
|
|
35148
|
+
}
|
|
34466
35149
|
return _resultRange[0][index];
|
|
34467
35150
|
},
|
|
34468
35151
|
isExported: true,
|
|
@@ -34483,7 +35166,9 @@ const MATCH = {
|
|
|
34483
35166
|
const _range = toMatrix(range);
|
|
34484
35167
|
const nbCol = _range.length;
|
|
34485
35168
|
const nbRow = _range[0].length;
|
|
34486
|
-
|
|
35169
|
+
if (nbCol !== 1 && nbRow !== 1) {
|
|
35170
|
+
return new EvaluationError(_t("The range must be a single row or a single column."));
|
|
35171
|
+
}
|
|
34487
35172
|
let index = -1;
|
|
34488
35173
|
const getElement = nbCol === 1
|
|
34489
35174
|
? (_range, index) => _range[0][index].value
|
|
@@ -34524,7 +35209,9 @@ const ROW = {
|
|
|
34524
35209
|
const row = cellReference === undefined
|
|
34525
35210
|
? this.__originCellPosition?.row
|
|
34526
35211
|
: toZone(cellReference.value).top;
|
|
34527
|
-
|
|
35212
|
+
if (row === undefined) {
|
|
35213
|
+
return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
|
|
35214
|
+
}
|
|
34528
35215
|
return row + 1;
|
|
34529
35216
|
},
|
|
34530
35217
|
isExported: true,
|
|
@@ -34558,7 +35245,9 @@ const VLOOKUP = {
|
|
|
34558
35245
|
compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
|
|
34559
35246
|
const _index = Math.trunc(toNumber(index?.value, this.locale));
|
|
34560
35247
|
const _range = toMatrix(range);
|
|
34561
|
-
|
|
35248
|
+
if (1 > _index || _index > _range.length) {
|
|
35249
|
+
return new EvaluationError(_t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
|
|
35250
|
+
}
|
|
34562
35251
|
const getValueFromRange = (range, index) => range[0][index].value;
|
|
34563
35252
|
const _isSorted = toBoolean(isSorted.value);
|
|
34564
35253
|
const rowIndex = _isSorted
|
|
@@ -34603,14 +35292,24 @@ const XLOOKUP = {
|
|
|
34603
35292
|
const _searchMode = Math.trunc(toNumber(searchMode.value, this.locale));
|
|
34604
35293
|
const _lookupRange = toMatrix(lookupRange);
|
|
34605
35294
|
const _returnRange = toMatrix(returnRange);
|
|
34606
|
-
|
|
34607
|
-
|
|
34608
|
-
|
|
35295
|
+
if (_lookupRange.length !== 1 && _lookupRange[0].length !== 1) {
|
|
35296
|
+
return new EvaluationError(_t("lookup_range should be either a single row or single column."));
|
|
35297
|
+
}
|
|
35298
|
+
if (![1, -1, 2, -2].includes(_searchMode)) {
|
|
35299
|
+
return new EvaluationError(_t("search_mode should be a value in [-1, 1, -2, 2]."));
|
|
35300
|
+
}
|
|
35301
|
+
if (![-1, 0, 1, 2].includes(_matchMode)) {
|
|
35302
|
+
return new EvaluationError(_t("match_mode should be a value in [-1, 0, 1, 2]."));
|
|
35303
|
+
}
|
|
34609
35304
|
const lookupDirection = _lookupRange.length === 1 ? "col" : "row";
|
|
34610
|
-
|
|
34611
|
-
|
|
34612
|
-
|
|
34613
|
-
|
|
35305
|
+
if (_matchMode === 2 && [-2, 2].includes(_searchMode)) {
|
|
35306
|
+
return new EvaluationError(_t("The search and match mode combination is not supported for XLOOKUP evaluation."));
|
|
35307
|
+
}
|
|
35308
|
+
if (lookupDirection === "col"
|
|
35309
|
+
? _returnRange[0].length !== _lookupRange[0].length
|
|
35310
|
+
: _returnRange.length !== _lookupRange.length) {
|
|
35311
|
+
return new EvaluationError(_t("return_range should have the same dimensions as lookup_range."));
|
|
35312
|
+
}
|
|
34614
35313
|
const getElement = lookupDirection === "col"
|
|
34615
35314
|
? (range, index) => range[0][index].value
|
|
34616
35315
|
: (range, index) => range[index][0].value;
|
|
@@ -34802,12 +35501,16 @@ const OFFSET = {
|
|
|
34802
35501
|
let offsetWidth = zone.right - zone.left + 1;
|
|
34803
35502
|
if (height) {
|
|
34804
35503
|
const _height = toNumber(height, this.locale);
|
|
34805
|
-
|
|
35504
|
+
if (_height < 1) {
|
|
35505
|
+
return new EvaluationError(_t("Height value is %(_height)s. It should be greater than or equal to 1.", { _height }));
|
|
35506
|
+
}
|
|
34806
35507
|
offsetHeight = _height;
|
|
34807
35508
|
}
|
|
34808
35509
|
if (width) {
|
|
34809
35510
|
const _width = toNumber(width, this.locale);
|
|
34810
|
-
|
|
35511
|
+
if (_width < 1) {
|
|
35512
|
+
return new EvaluationError(_t("Width value is %(_width)s. It should be greater than or equal to 1.", { _width }));
|
|
35513
|
+
}
|
|
34811
35514
|
offsetWidth = _width;
|
|
34812
35515
|
}
|
|
34813
35516
|
const { sheetName } = splitReference(_cellReference);
|
|
@@ -34905,7 +35608,9 @@ const DIVIDE = {
|
|
|
34905
35608
|
],
|
|
34906
35609
|
compute: function (dividend, divisor) {
|
|
34907
35610
|
const _divisor = toNumber(divisor, this.locale);
|
|
34908
|
-
|
|
35611
|
+
if (_divisor === 0) {
|
|
35612
|
+
return new DivisionByZeroError(_t("The divisor must be different from zero."));
|
|
35613
|
+
}
|
|
34909
35614
|
return {
|
|
34910
35615
|
value: toNumber(dividend, this.locale) / _divisor,
|
|
34911
35616
|
format: dividend?.format || divisor?.format,
|
|
@@ -35476,7 +36181,9 @@ const CHAR = {
|
|
|
35476
36181
|
],
|
|
35477
36182
|
compute: function (tableNumber) {
|
|
35478
36183
|
const _tableNumber = Math.trunc(toNumber(tableNumber, this.locale));
|
|
35479
|
-
|
|
36184
|
+
if (_tableNumber < 1) {
|
|
36185
|
+
return new EvaluationError(_t("The table_number (%s) is out of range.", _tableNumber));
|
|
36186
|
+
}
|
|
35480
36187
|
return String.fromCharCode(_tableNumber);
|
|
35481
36188
|
},
|
|
35482
36189
|
isExported: true,
|
|
@@ -35541,10 +36248,16 @@ const FIND = {
|
|
|
35541
36248
|
const _searchFor = toString(searchFor);
|
|
35542
36249
|
const _textToSearch = toString(textToSearch);
|
|
35543
36250
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
35544
|
-
|
|
35545
|
-
|
|
36251
|
+
if (_textToSearch === "") {
|
|
36252
|
+
return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
36253
|
+
}
|
|
36254
|
+
if (_startingAt < 1) {
|
|
36255
|
+
return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
36256
|
+
}
|
|
35546
36257
|
const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);
|
|
35547
|
-
|
|
36258
|
+
if (result === -1) {
|
|
36259
|
+
return new EvaluationError(_t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch));
|
|
36260
|
+
}
|
|
35548
36261
|
return result + 1;
|
|
35549
36262
|
},
|
|
35550
36263
|
isExported: true,
|
|
@@ -35575,7 +36288,9 @@ const LEFT = {
|
|
|
35575
36288
|
],
|
|
35576
36289
|
compute: function (text, ...args) {
|
|
35577
36290
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
35578
|
-
|
|
36291
|
+
if (_numberOfCharacters < 0) {
|
|
36292
|
+
return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
36293
|
+
}
|
|
35579
36294
|
return toString(text).substring(0, _numberOfCharacters);
|
|
35580
36295
|
},
|
|
35581
36296
|
isExported: true,
|
|
@@ -35616,8 +36331,12 @@ const MID = {
|
|
|
35616
36331
|
const _text = toString(text);
|
|
35617
36332
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
35618
36333
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
35619
|
-
|
|
35620
|
-
|
|
36334
|
+
if (_starting_at < 1) {
|
|
36335
|
+
return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
36336
|
+
}
|
|
36337
|
+
if (_extract_length < 0) {
|
|
36338
|
+
return new EvaluationError(_t("The extract_length argument (%s) must be positive or null.", _extract_length));
|
|
36339
|
+
}
|
|
35621
36340
|
return _text.slice(_starting_at - 1, _starting_at + _extract_length - 1);
|
|
35622
36341
|
},
|
|
35623
36342
|
isExported: true,
|
|
@@ -35651,7 +36370,9 @@ const REPLACE = {
|
|
|
35651
36370
|
],
|
|
35652
36371
|
compute: function (text, position, length, newText) {
|
|
35653
36372
|
const _position = toNumber(position, this.locale);
|
|
35654
|
-
|
|
36373
|
+
if (_position < 1) {
|
|
36374
|
+
return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
36375
|
+
}
|
|
35655
36376
|
const _text = toString(text);
|
|
35656
36377
|
const _length = toNumber(length, this.locale);
|
|
35657
36378
|
const _newText = toString(newText);
|
|
@@ -35670,7 +36391,9 @@ const RIGHT = {
|
|
|
35670
36391
|
],
|
|
35671
36392
|
compute: function (text, ...args) {
|
|
35672
36393
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
35673
|
-
|
|
36394
|
+
if (_numberOfCharacters < 0) {
|
|
36395
|
+
return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
36396
|
+
}
|
|
35674
36397
|
const _text = toString(text);
|
|
35675
36398
|
const stringLength = _text.length;
|
|
35676
36399
|
return _text.substring(stringLength - _numberOfCharacters, stringLength);
|
|
@@ -35733,7 +36456,9 @@ const SPLIT = {
|
|
|
35733
36456
|
const _delimiter = escapeRegExp(toString(delimiter));
|
|
35734
36457
|
const _splitByEach = toBoolean(splitByEach);
|
|
35735
36458
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
35736
|
-
|
|
36459
|
+
if (_delimiter.length <= 0) {
|
|
36460
|
+
return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
36461
|
+
}
|
|
35737
36462
|
const regex = _splitByEach ? new RegExp(`[${_delimiter}]`, "g") : new RegExp(_delimiter, "g");
|
|
35738
36463
|
let result = _text.split(regex);
|
|
35739
36464
|
if (_removeEmptyText) {
|
|
@@ -35756,7 +36481,9 @@ const SUBSTITUTE = {
|
|
|
35756
36481
|
],
|
|
35757
36482
|
compute: function (textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
35758
36483
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
35759
|
-
|
|
36484
|
+
if (_occurrenceNumber < 0) {
|
|
36485
|
+
return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
36486
|
+
}
|
|
35760
36487
|
const _textToSearch = toString(textToSearch);
|
|
35761
36488
|
const _searchFor = toString(searchFor);
|
|
35762
36489
|
if (_searchFor === "") {
|
|
@@ -35916,6 +36643,13 @@ const functionNameRegex = /^[A-Z0-9\_\.]+$/;
|
|
|
35916
36643
|
class FunctionRegistry extends Registry {
|
|
35917
36644
|
mapping = {};
|
|
35918
36645
|
add(name, addDescr) {
|
|
36646
|
+
name = name.toUpperCase();
|
|
36647
|
+
if (name in this.content) {
|
|
36648
|
+
throw new Error(`${name} is already present in this registry!`);
|
|
36649
|
+
}
|
|
36650
|
+
return this.replace(name, addDescr);
|
|
36651
|
+
}
|
|
36652
|
+
replace(name, addDescr) {
|
|
35919
36653
|
name = name.toUpperCase();
|
|
35920
36654
|
if (!functionNameRegex.test(name)) {
|
|
35921
36655
|
throw new Error(_t("Invalid function name %s. Function names can exclusively contain alphanumerical values separated by dots (.) or underscore (_)", name));
|
|
@@ -35923,7 +36657,7 @@ class FunctionRegistry extends Registry {
|
|
|
35923
36657
|
const descr = addMetaInfoFromArg(name, addDescr);
|
|
35924
36658
|
validateArguments(descr);
|
|
35925
36659
|
this.mapping[name] = createComputeFunction(descr);
|
|
35926
|
-
super.
|
|
36660
|
+
super.replace(name, descr);
|
|
35927
36661
|
return this;
|
|
35928
36662
|
}
|
|
35929
36663
|
}
|
|
@@ -40437,6 +41171,13 @@ class Composer extends owl.Component {
|
|
|
40437
41171
|
openAssistant() {
|
|
40438
41172
|
this.assistant.forcedClosed = false;
|
|
40439
41173
|
}
|
|
41174
|
+
onWheel(event) {
|
|
41175
|
+
// detect if scrollbar is available
|
|
41176
|
+
if (this.composerRef.el &&
|
|
41177
|
+
this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
|
|
41178
|
+
event.stopPropagation();
|
|
41179
|
+
}
|
|
41180
|
+
}
|
|
40440
41181
|
// ---------------------------------------------------------------------------
|
|
40441
41182
|
// Private
|
|
40442
41183
|
// ---------------------------------------------------------------------------
|
|
@@ -40716,6 +41457,8 @@ class GridComposer extends owl.Component {
|
|
|
40716
41457
|
}
|
|
40717
41458
|
get composerProps() {
|
|
40718
41459
|
const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
|
|
41460
|
+
// Remove the wrapper border width
|
|
41461
|
+
const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
|
|
40719
41462
|
return {
|
|
40720
41463
|
rect: { ...this.rect },
|
|
40721
41464
|
delimitation: {
|
|
@@ -40733,6 +41476,7 @@ class GridComposer extends owl.Component {
|
|
|
40733
41476
|
}),
|
|
40734
41477
|
onInputContextMenu: this.props.onInputContextMenu,
|
|
40735
41478
|
composerStore: this.composerStore,
|
|
41479
|
+
inputStyle: `max-height: ${maxHeight}px;`,
|
|
40736
41480
|
};
|
|
40737
41481
|
}
|
|
40738
41482
|
get containerStyle() {
|
|
@@ -44312,6 +45056,7 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
44312
45056
|
initialRanges;
|
|
44313
45057
|
inputHasSingleRange;
|
|
44314
45058
|
colors;
|
|
45059
|
+
disabledRanges;
|
|
44315
45060
|
mutators = [
|
|
44316
45061
|
"resetWithRanges",
|
|
44317
45062
|
"focusById",
|
|
@@ -44322,17 +45067,19 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
44322
45067
|
"reset",
|
|
44323
45068
|
"confirm",
|
|
44324
45069
|
"updateColors",
|
|
45070
|
+
"updateDisabledRanges",
|
|
44325
45071
|
];
|
|
44326
45072
|
ranges = [];
|
|
44327
45073
|
focusedRangeIndex = null;
|
|
44328
45074
|
inputSheetId;
|
|
44329
45075
|
focusStore = this.get(FocusStore);
|
|
44330
45076
|
highlightStore = this.get(HighlightStore);
|
|
44331
|
-
constructor(get, initialRanges = [], inputHasSingleRange = false, colors = []) {
|
|
45077
|
+
constructor(get, initialRanges = [], inputHasSingleRange = false, colors = [], disabledRanges = []) {
|
|
44332
45078
|
super(get);
|
|
44333
45079
|
this.initialRanges = initialRanges;
|
|
44334
45080
|
this.inputHasSingleRange = inputHasSingleRange;
|
|
44335
45081
|
this.colors = colors;
|
|
45082
|
+
this.disabledRanges = disabledRanges;
|
|
44336
45083
|
if (inputHasSingleRange && initialRanges.length > 1) {
|
|
44337
45084
|
throw new Error("Input with a single range cannot be instantiated with several range references.");
|
|
44338
45085
|
}
|
|
@@ -44449,6 +45196,9 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
44449
45196
|
color: colorGenerator.next(),
|
|
44450
45197
|
}));
|
|
44451
45198
|
}
|
|
45199
|
+
updateDisabledRanges(disabledRanges) {
|
|
45200
|
+
this.disabledRanges = disabledRanges;
|
|
45201
|
+
}
|
|
44452
45202
|
confirm() {
|
|
44453
45203
|
for (const range of this.selectionInputs) {
|
|
44454
45204
|
if (range.xc === "") {
|
|
@@ -44490,6 +45240,7 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
44490
45240
|
: null,
|
|
44491
45241
|
isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
|
|
44492
45242
|
isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
|
|
45243
|
+
disabled: this.disabledRanges?.[index],
|
|
44493
45244
|
}));
|
|
44494
45245
|
}
|
|
44495
45246
|
get isResettable() {
|
|
@@ -44654,7 +45405,7 @@ css /* scss */ `
|
|
|
44654
45405
|
input.o-invalid {
|
|
44655
45406
|
background-color: ${ALERT_DANGER_BG};
|
|
44656
45407
|
}
|
|
44657
|
-
.
|
|
45408
|
+
.input-icon {
|
|
44658
45409
|
right: 7px;
|
|
44659
45410
|
top: 4px;
|
|
44660
45411
|
}
|
|
@@ -44662,6 +45413,9 @@ css /* scss */ `
|
|
|
44662
45413
|
cursor: move;
|
|
44663
45414
|
}
|
|
44664
45415
|
}
|
|
45416
|
+
.o-disabled-ranges {
|
|
45417
|
+
color: #888 !important;
|
|
45418
|
+
}
|
|
44665
45419
|
.o-button {
|
|
44666
45420
|
flex-grow: 0;
|
|
44667
45421
|
}
|
|
@@ -44694,6 +45448,8 @@ class SelectionInput extends owl.Component {
|
|
|
44694
45448
|
onSelectionReordered: { type: Function, optional: true },
|
|
44695
45449
|
onSelectionRemoved: { type: Function, optional: true },
|
|
44696
45450
|
colors: { type: Array, optional: true, default: [] },
|
|
45451
|
+
disabledRanges: { type: Array, optional: true, default: [] },
|
|
45452
|
+
disabledRangeTitle: { type: String, optional: true },
|
|
44697
45453
|
};
|
|
44698
45454
|
state = owl.useState({
|
|
44699
45455
|
isMissing: false,
|
|
@@ -44718,9 +45474,12 @@ class SelectionInput extends owl.Component {
|
|
|
44718
45474
|
get isResettable() {
|
|
44719
45475
|
return this.store.isResettable;
|
|
44720
45476
|
}
|
|
45477
|
+
get hasDisabledRanges() {
|
|
45478
|
+
return this.store.disabledRanges.some(Boolean);
|
|
45479
|
+
}
|
|
44721
45480
|
setup() {
|
|
44722
45481
|
owl.useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
|
|
44723
|
-
this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false, this.props.colors);
|
|
45482
|
+
this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false, this.props.colors, this.props.disabledRanges);
|
|
44724
45483
|
owl.onWillUpdateProps((nextProps) => {
|
|
44725
45484
|
if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
|
|
44726
45485
|
this.triggerChange();
|
|
@@ -44733,6 +45492,10 @@ class SelectionInput extends owl.Component {
|
|
|
44733
45492
|
nextProps.colors?.join() !== this.store.colors.join()) {
|
|
44734
45493
|
this.store.updateColors(nextProps.colors || []);
|
|
44735
45494
|
}
|
|
45495
|
+
if (!deepEquals(nextProps.disabledRanges, this.props.disabledRanges) &&
|
|
45496
|
+
!deepEquals(nextProps.disabledRanges, this.store.disabledRanges)) {
|
|
45497
|
+
this.store.updateDisabledRanges(nextProps.disabledRanges || []);
|
|
45498
|
+
}
|
|
44736
45499
|
});
|
|
44737
45500
|
}
|
|
44738
45501
|
startDragAndDrop(rangeId, event) {
|
|
@@ -44848,10 +45611,14 @@ class ChartDataSeries extends owl.Component {
|
|
|
44848
45611
|
onSelectionRemoved: { type: Function, optional: true },
|
|
44849
45612
|
onSelectionConfirmed: Function,
|
|
44850
45613
|
title: { type: String, optional: true },
|
|
45614
|
+
maxNumberOfUsedRanges: { type: Number, optional: true },
|
|
44851
45615
|
};
|
|
44852
45616
|
get ranges() {
|
|
44853
45617
|
return this.props.ranges.map((r) => r.dataRange);
|
|
44854
45618
|
}
|
|
45619
|
+
get disabledRanges() {
|
|
45620
|
+
return this.props.ranges.map((r, i) => this.props.maxNumberOfUsedRanges ? i >= this.props.maxNumberOfUsedRanges : false);
|
|
45621
|
+
}
|
|
44855
45622
|
get colors() {
|
|
44856
45623
|
return this.props.ranges.map((r) => r.backgroundColor);
|
|
44857
45624
|
}
|
|
@@ -44916,7 +45683,7 @@ class GenericChartConfigPanel extends owl.Component {
|
|
|
44916
45683
|
const cancelledReasons = [
|
|
44917
45684
|
...(this.state.datasetDispatchResult?.reasons || []),
|
|
44918
45685
|
...(this.state.labelsDispatchResult?.reasons || []),
|
|
44919
|
-
];
|
|
45686
|
+
].filter((reason) => reason !== "NoChanges" /* CommandResult.NoChanges */);
|
|
44920
45687
|
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
|
|
44921
45688
|
}
|
|
44922
45689
|
get isDatasetInvalid() {
|
|
@@ -45038,6 +45805,9 @@ class GenericChartConfigPanel extends owl.Component {
|
|
|
45038
45805
|
}
|
|
45039
45806
|
return undefined;
|
|
45040
45807
|
}
|
|
45808
|
+
get maxNumberOfUsedRanges() {
|
|
45809
|
+
return chartRegistry.get(this.props.definition.type).dataSeriesLimit;
|
|
45810
|
+
}
|
|
45041
45811
|
}
|
|
45042
45812
|
|
|
45043
45813
|
class BarConfigPanel extends GenericChartConfigPanel {
|
|
@@ -47196,13 +47966,10 @@ class GeoChartConfigPanel extends GenericChartConfigPanel {
|
|
|
47196
47966
|
static template = "o-spreadsheet-GeoChartConfigPanel";
|
|
47197
47967
|
static components = { ...GenericChartConfigPanel.components, GeoChartRegionSelectSection };
|
|
47198
47968
|
get dataRanges() {
|
|
47199
|
-
return this.getDataSeriesRanges()
|
|
47969
|
+
return this.getDataSeriesRanges();
|
|
47200
47970
|
}
|
|
47201
|
-
|
|
47202
|
-
|
|
47203
|
-
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
|
|
47204
|
-
dataSets: this.dataSets,
|
|
47205
|
-
});
|
|
47971
|
+
get disabledRanges() {
|
|
47972
|
+
return this.props.definition.dataSets.map((ds, i) => i > 0);
|
|
47206
47973
|
}
|
|
47207
47974
|
getLabelRangeOptions() {
|
|
47208
47975
|
return [
|
|
@@ -47329,6 +48096,19 @@ class LineConfigPanel extends GenericChartConfigPanel {
|
|
|
47329
48096
|
}
|
|
47330
48097
|
}
|
|
47331
48098
|
|
|
48099
|
+
class PieHoleSize extends owl.Component {
|
|
48100
|
+
static template = "o-spreadsheet.PieHoleSize";
|
|
48101
|
+
static components = { Section };
|
|
48102
|
+
static props = { onValueChange: Function, value: Number };
|
|
48103
|
+
// Very short debounce to prevent up/down arrow on number input to spam the onChange
|
|
48104
|
+
debouncedOnChange = debounce(this.onChange.bind(this), 100);
|
|
48105
|
+
onChange(value) {
|
|
48106
|
+
if (!isNaN(Number(value))) {
|
|
48107
|
+
this.props.onValueChange(clip(Number(value), 0, 95));
|
|
48108
|
+
}
|
|
48109
|
+
}
|
|
48110
|
+
}
|
|
48111
|
+
|
|
47332
48112
|
class PieChartDesignPanel extends owl.Component {
|
|
47333
48113
|
static template = "o-spreadsheet-PieChartDesignPanel";
|
|
47334
48114
|
static components = {
|
|
@@ -47336,6 +48116,7 @@ class PieChartDesignPanel extends owl.Component {
|
|
|
47336
48116
|
Section,
|
|
47337
48117
|
Checkbox,
|
|
47338
48118
|
ChartLegend,
|
|
48119
|
+
PieHoleSize,
|
|
47339
48120
|
};
|
|
47340
48121
|
static props = {
|
|
47341
48122
|
figureId: String,
|
|
@@ -47343,6 +48124,12 @@ class PieChartDesignPanel extends owl.Component {
|
|
|
47343
48124
|
updateChart: Function,
|
|
47344
48125
|
canUpdateChart: { type: Function, optional: true },
|
|
47345
48126
|
};
|
|
48127
|
+
onPieHoleSizeChange(pieHolePercentage) {
|
|
48128
|
+
this.props.updateChart(this.props.figureId, {
|
|
48129
|
+
...this.props.definition,
|
|
48130
|
+
pieHolePercentage,
|
|
48131
|
+
});
|
|
48132
|
+
}
|
|
47346
48133
|
}
|
|
47347
48134
|
|
|
47348
48135
|
class RadarChartDesignPanel extends owl.Component {
|
|
@@ -47511,6 +48298,7 @@ class SunburstChartDesignPanel extends owl.Component {
|
|
|
47511
48298
|
TextStyler,
|
|
47512
48299
|
RoundColorPicker,
|
|
47513
48300
|
ChartLegend,
|
|
48301
|
+
PieHoleSize,
|
|
47514
48302
|
};
|
|
47515
48303
|
static props = {
|
|
47516
48304
|
figureId: String,
|
|
@@ -47536,6 +48324,12 @@ class SunburstChartDesignPanel extends owl.Component {
|
|
|
47536
48324
|
colors[index] = color;
|
|
47537
48325
|
this.props.updateChart(this.props.figureId, { groupColors: colors });
|
|
47538
48326
|
}
|
|
48327
|
+
onPieHoleSizeChange(pieHolePercentage) {
|
|
48328
|
+
this.props.updateChart(this.props.figureId, {
|
|
48329
|
+
...this.props.definition,
|
|
48330
|
+
pieHolePercentage,
|
|
48331
|
+
});
|
|
48332
|
+
}
|
|
47539
48333
|
}
|
|
47540
48334
|
|
|
47541
48335
|
class TreeMapCategoryColors extends owl.Component {
|
|
@@ -51960,8 +52754,8 @@ function compareDimensionValues(dimension, a, b) {
|
|
|
51960
52754
|
|
|
51961
52755
|
const NULL_SYMBOL = Symbol("NULL");
|
|
51962
52756
|
function createDate(dimension, value, locale) {
|
|
51963
|
-
const granularity = dimension.granularity;
|
|
51964
|
-
if (!
|
|
52757
|
+
const granularity = dimension.granularity || "month";
|
|
52758
|
+
if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
|
|
51965
52759
|
throw new Error(`Unknown date granularity: ${granularity}`);
|
|
51966
52760
|
}
|
|
51967
52761
|
const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
|
|
@@ -51980,6 +52774,9 @@ function createDate(dimension, value, locale) {
|
|
|
51980
52774
|
case "month_number":
|
|
51981
52775
|
number = date.getMonth() + 1;
|
|
51982
52776
|
break;
|
|
52777
|
+
case "month":
|
|
52778
|
+
number = Math.floor(toNumber(value, locale));
|
|
52779
|
+
break;
|
|
51983
52780
|
case "iso_week_number":
|
|
51984
52781
|
number = date.getIsoWeek();
|
|
51985
52782
|
break;
|
|
@@ -52073,6 +52870,10 @@ const MAP_VALUE_DIMENSION_DATE = {
|
|
|
52073
52870
|
set: new Set(),
|
|
52074
52871
|
values: {},
|
|
52075
52872
|
},
|
|
52873
|
+
month: {
|
|
52874
|
+
set: new Set(),
|
|
52875
|
+
values: {},
|
|
52876
|
+
},
|
|
52076
52877
|
iso_week_number: {
|
|
52077
52878
|
set: new Set(),
|
|
52078
52879
|
values: {},
|
|
@@ -52283,7 +53084,7 @@ class SpreadsheetPivot {
|
|
|
52283
53084
|
const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
|
|
52284
53085
|
const finalCell = cells[0]?.[dimension.nameWithGranularity];
|
|
52285
53086
|
if (dimension.type === "datetime") {
|
|
52286
|
-
const adapter = pivotTimeAdapter(dimension.granularity);
|
|
53087
|
+
const adapter = pivotTimeAdapter((dimension.granularity || "month"));
|
|
52287
53088
|
return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
|
|
52288
53089
|
}
|
|
52289
53090
|
if (!finalCell) {
|
|
@@ -52401,7 +53202,7 @@ class SpreadsheetPivot {
|
|
|
52401
53202
|
if (nonEmptyCells.length === 0) {
|
|
52402
53203
|
return "integer";
|
|
52403
53204
|
}
|
|
52404
|
-
if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
|
|
53205
|
+
if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
|
|
52405
53206
|
return "datetime";
|
|
52406
53207
|
}
|
|
52407
53208
|
if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
|
|
@@ -52494,7 +53295,7 @@ class SpreadsheetPivot {
|
|
|
52494
53295
|
for (const entry of dataEntries) {
|
|
52495
53296
|
for (const dimension of dateDimensions) {
|
|
52496
53297
|
const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
|
|
52497
|
-
const adapter = pivotTimeAdapter(dimension.granularity);
|
|
53298
|
+
const adapter = pivotTimeAdapter((dimension.granularity || "month"));
|
|
52498
53299
|
const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
|
|
52499
53300
|
entry[dimension.nameWithGranularity] = {
|
|
52500
53301
|
value,
|
|
@@ -52514,6 +53315,7 @@ const dateGranularities = [
|
|
|
52514
53315
|
"year",
|
|
52515
53316
|
"quarter_number",
|
|
52516
53317
|
"month_number",
|
|
53318
|
+
"month",
|
|
52517
53319
|
"iso_week_number",
|
|
52518
53320
|
"day_of_month",
|
|
52519
53321
|
"day",
|
|
@@ -52764,7 +53566,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
52764
53566
|
: this.datetimeGranularities);
|
|
52765
53567
|
}
|
|
52766
53568
|
for (const field of dateFields) {
|
|
52767
|
-
granularitiesPerFields[field.fieldName].delete(field.granularity);
|
|
53569
|
+
granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
|
|
52768
53570
|
}
|
|
52769
53571
|
return granularitiesPerFields;
|
|
52770
53572
|
}
|
|
@@ -56000,7 +56802,7 @@ class ChartPlugin extends CorePlugin {
|
|
|
56000
56802
|
case "CREATE_CHART":
|
|
56001
56803
|
return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
|
|
56002
56804
|
case "UPDATE_CHART":
|
|
56003
|
-
return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));
|
|
56805
|
+
return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists, this.checkChartChanged));
|
|
56004
56806
|
default:
|
|
56005
56807
|
return "Success" /* CommandResult.Success */;
|
|
56006
56808
|
}
|
|
@@ -56154,10 +56956,15 @@ class ChartPlugin extends CorePlugin {
|
|
|
56154
56956
|
: "Success" /* CommandResult.Success */;
|
|
56155
56957
|
}
|
|
56156
56958
|
checkChartExists(cmd) {
|
|
56157
|
-
return this.
|
|
56959
|
+
return this.isChartDefined(cmd.figureId)
|
|
56158
56960
|
? "Success" /* CommandResult.Success */
|
|
56159
56961
|
: "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
|
|
56160
56962
|
}
|
|
56963
|
+
checkChartChanged(cmd) {
|
|
56964
|
+
return deepEquals(this.getChartDefinition(cmd.figureId), cmd.definition)
|
|
56965
|
+
? "NoChanges" /* CommandResult.NoChanges */
|
|
56966
|
+
: "Success" /* CommandResult.Success */;
|
|
56967
|
+
}
|
|
56161
56968
|
}
|
|
56162
56969
|
|
|
56163
56970
|
// -----------------------------------------------------------------------------
|
|
@@ -58416,6 +59223,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
58416
59223
|
"getCommandZones",
|
|
58417
59224
|
"getUnboundedZone",
|
|
58418
59225
|
"checkElementsIncludeAllNonFrozenHeaders",
|
|
59226
|
+
"getDuplicateSheetName",
|
|
58419
59227
|
];
|
|
58420
59228
|
sheetIdsMapName = {};
|
|
58421
59229
|
orderedSheetIds = [];
|
|
@@ -58440,7 +59248,11 @@ class SheetPlugin extends CorePlugin {
|
|
|
58440
59248
|
return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
|
|
58441
59249
|
}
|
|
58442
59250
|
case "DUPLICATE_SHEET": {
|
|
58443
|
-
|
|
59251
|
+
if (this.sheets[cmd.sheetIdTo])
|
|
59252
|
+
return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
|
|
59253
|
+
if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
|
|
59254
|
+
return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
|
|
59255
|
+
return "Success" /* CommandResult.Success */;
|
|
58444
59256
|
}
|
|
58445
59257
|
case "MOVE_SHEET":
|
|
58446
59258
|
try {
|
|
@@ -58517,7 +59329,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
58517
59329
|
this.showSheet(cmd.sheetId);
|
|
58518
59330
|
break;
|
|
58519
59331
|
case "DUPLICATE_SHEET":
|
|
58520
|
-
this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
|
|
59332
|
+
this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
|
|
58521
59333
|
break;
|
|
58522
59334
|
case "DELETE_SHEET":
|
|
58523
59335
|
this.deleteSheet(this.sheets[cmd.sheetId]);
|
|
@@ -58724,10 +59536,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
58724
59536
|
}
|
|
58725
59537
|
getNextSheetName(baseName = "Sheet") {
|
|
58726
59538
|
const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
|
|
58727
|
-
return
|
|
58728
|
-
compute: (name, i) => `${name}${i}`,
|
|
58729
|
-
computeFirstOne: true,
|
|
58730
|
-
});
|
|
59539
|
+
return getNextSheetName(names, baseName);
|
|
58731
59540
|
}
|
|
58732
59541
|
getSheetSize(sheetId) {
|
|
58733
59542
|
return {
|
|
@@ -58974,9 +59783,8 @@ class SheetPlugin extends CorePlugin {
|
|
|
58974
59783
|
showSheet(sheetId) {
|
|
58975
59784
|
this.history.update("sheets", sheetId, "isVisible", true);
|
|
58976
59785
|
}
|
|
58977
|
-
duplicateSheet(fromId, toId) {
|
|
59786
|
+
duplicateSheet(fromId, toId, toName) {
|
|
58978
59787
|
const sheet = this.getSheet(fromId);
|
|
58979
|
-
const toName = this.getDuplicateSheetName(sheet.name);
|
|
58980
59788
|
const newSheet = deepCopy(sheet);
|
|
58981
59789
|
newSheet.id = toId;
|
|
58982
59790
|
newSheet.name = toName;
|
|
@@ -59009,8 +59817,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
59009
59817
|
}
|
|
59010
59818
|
getDuplicateSheetName(sheetName) {
|
|
59011
59819
|
const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
|
|
59012
|
-
|
|
59013
|
-
return getUniqueText(baseName.toString(), names);
|
|
59820
|
+
return getDuplicateSheetName(sheetName, names);
|
|
59014
59821
|
}
|
|
59015
59822
|
deleteSheet(sheet) {
|
|
59016
59823
|
const name = sheet.name;
|
|
@@ -61814,8 +62621,8 @@ class SpreadingRelation {
|
|
|
61814
62621
|
const EMPTY_ARRAY = [];
|
|
61815
62622
|
|
|
61816
62623
|
const MAX_ITERATION = 30;
|
|
61817
|
-
const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
|
|
61818
|
-
const EMPTY_CELL = createEvaluatedCell({ value: null });
|
|
62624
|
+
const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
|
|
62625
|
+
const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
|
|
61819
62626
|
class Evaluator {
|
|
61820
62627
|
context;
|
|
61821
62628
|
getters;
|
|
@@ -65663,6 +66470,10 @@ class SpecificRangeTransformRegistry extends Registry {
|
|
|
65663
66470
|
super.add(cmdType, fn);
|
|
65664
66471
|
return this;
|
|
65665
66472
|
}
|
|
66473
|
+
replace(cmdType, fn) {
|
|
66474
|
+
super.replace(cmdType, fn);
|
|
66475
|
+
return this;
|
|
66476
|
+
}
|
|
65666
66477
|
get(cmdType) {
|
|
65667
66478
|
return this.content[cmdType];
|
|
65668
66479
|
}
|
|
@@ -68242,7 +69053,6 @@ repeatCommandTransformRegistry.add("CREATE_CHART", repeatCreateChartCommand);
|
|
|
68242
69053
|
repeatCommandTransformRegistry.add("CREATE_IMAGE", repeatCreateImageCommand);
|
|
68243
69054
|
repeatCommandTransformRegistry.add("GROUP_HEADERS", repeatGroupHeadersCommand);
|
|
68244
69055
|
repeatCommandTransformRegistry.add("UNGROUP_HEADERS", repeatGroupHeadersCommand);
|
|
68245
|
-
repeatCommandTransformRegistry.add("UNGROUP_HEADERS", repeatGroupHeadersCommand);
|
|
68246
69056
|
repeatCommandTransformRegistry.add("UNFOLD_HEADER_GROUPS_IN_ZONE", repeatZoneDependantCommand);
|
|
68247
69057
|
repeatCommandTransformRegistry.add("FOLD_HEADER_GROUPS_IN_ZONE", repeatZoneDependantCommand);
|
|
68248
69058
|
const repeatLocalCommandTransformRegistry = new Registry();
|
|
@@ -72545,9 +73355,9 @@ topbarMenuRegistry
|
|
|
72545
73355
|
class TopBarComponentRegistry extends Registry {
|
|
72546
73356
|
mapping = {};
|
|
72547
73357
|
uuidGenerator = new UuidGenerator();
|
|
72548
|
-
|
|
73358
|
+
replace(name, value) {
|
|
72549
73359
|
const component = { ...value, id: this.uuidGenerator.uuidv4() };
|
|
72550
|
-
return super.
|
|
73360
|
+
return super.replace(name, component);
|
|
72551
73361
|
}
|
|
72552
73362
|
getAllOrdered() {
|
|
72553
73363
|
return this.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
@@ -74111,6 +74921,9 @@ class TopBarToolStore {
|
|
|
74111
74921
|
class ToolBarRegistry {
|
|
74112
74922
|
content = {};
|
|
74113
74923
|
add(key) {
|
|
74924
|
+
if (key in this.content) {
|
|
74925
|
+
throw new Error(`${key} is already present in this registry!`);
|
|
74926
|
+
}
|
|
74114
74927
|
this.content[key] = [];
|
|
74115
74928
|
return this;
|
|
74116
74929
|
}
|
|
@@ -75471,11 +76284,13 @@ class Spreadsheet extends owl.Component {
|
|
|
75471
76284
|
this.checkViewportSize();
|
|
75472
76285
|
stores.on("store-updated", this, render);
|
|
75473
76286
|
resizeObserver.observe(this.spreadsheetRef.el);
|
|
76287
|
+
registerChartJSExtensions();
|
|
75474
76288
|
});
|
|
75475
76289
|
owl.onWillUnmount(() => {
|
|
75476
76290
|
this.unbindModelEvents();
|
|
75477
76291
|
stores.off("store-updated", this);
|
|
75478
76292
|
resizeObserver.disconnect();
|
|
76293
|
+
unregisterChartJsExtensions();
|
|
75479
76294
|
});
|
|
75480
76295
|
owl.onPatched(() => {
|
|
75481
76296
|
this.checkViewportSize();
|
|
@@ -79978,6 +80793,7 @@ const components = {
|
|
|
79978
80793
|
SidePanelCollapsible,
|
|
79979
80794
|
RadioSelection,
|
|
79980
80795
|
GeoChartRegionSelectSection,
|
|
80796
|
+
ChartDashboardMenu,
|
|
79981
80797
|
};
|
|
79982
80798
|
const hooks = {
|
|
79983
80799
|
useDragAndDropListItems,
|
|
@@ -80068,6 +80884,6 @@ exports.tokenColors = tokenColors;
|
|
|
80068
80884
|
exports.tokenize = tokenize;
|
|
80069
80885
|
|
|
80070
80886
|
|
|
80071
|
-
__info__.version = "18.4.0-alpha.
|
|
80072
|
-
__info__.date = "2025-05-
|
|
80073
|
-
__info__.hash = "
|
|
80887
|
+
__info__.version = "18.4.0-alpha.2";
|
|
80888
|
+
__info__.date = "2025-05-12T05:28:06.109Z";
|
|
80889
|
+
__info__.hash = "a11158e";
|