@odoo/o-spreadsheet 17.5.0-alpha.2 → 17.5.0-alpha.3
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 +886 -541
- package/dist/o-spreadsheet.d.ts +47 -19
- package/dist/o-spreadsheet.esm.js +886 -541
- package/dist/o-spreadsheet.iife.js +886 -541
- package/dist/o-spreadsheet.iife.min.js +357 -347
- package/dist/o_spreadsheet.xml +56 -7
- package/package.json +2 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 17.5.0-alpha.
|
|
6
|
-
* @date 2024-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.5.0-alpha.3
|
|
6
|
+
* @date 2024-08-02T08:24:53.425Z
|
|
7
|
+
* @hash 767725f
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -2240,6 +2240,7 @@ exports.CommandResult = void 0;
|
|
|
2240
2240
|
CommandResult["InvalidTableResize"] = "InvalidTableResize";
|
|
2241
2241
|
CommandResult["PivotIdNotFound"] = "PivotIdNotFound";
|
|
2242
2242
|
CommandResult["EmptyName"] = "EmptyName";
|
|
2243
|
+
CommandResult["ValueCellIsInvalidFormula"] = "ValueCellIsInvalidFormula";
|
|
2243
2244
|
})(exports.CommandResult || (exports.CommandResult = {}));
|
|
2244
2245
|
|
|
2245
2246
|
const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
|
|
@@ -2767,13 +2768,16 @@ const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
|
|
|
2767
2768
|
}
|
|
2768
2769
|
return new RegExp("^" + exp + "$", "i");
|
|
2769
2770
|
});
|
|
2770
|
-
function evaluatePredicate(value = "", criterion) {
|
|
2771
|
+
function evaluatePredicate(value = "", criterion, locale) {
|
|
2771
2772
|
const { operator, operand } = criterion;
|
|
2772
2773
|
if (operand === undefined || value === null || operand === null) {
|
|
2773
2774
|
return false;
|
|
2774
2775
|
}
|
|
2775
2776
|
if (typeof operand === "number" && operator === "=") {
|
|
2776
|
-
|
|
2777
|
+
if (typeof value === "string" && (isNumber(value, locale) || isDateTime(value, locale))) {
|
|
2778
|
+
return toNumber(value, locale) === operand;
|
|
2779
|
+
}
|
|
2780
|
+
return value === operand;
|
|
2777
2781
|
}
|
|
2778
2782
|
if (operator === "<>" || operator === "=") {
|
|
2779
2783
|
let result;
|
|
@@ -2855,7 +2859,7 @@ function visitMatchingRanges(args, cb, locale, isQuery = false) {
|
|
|
2855
2859
|
for (let k = 0; k < countArg - 1; k += 2) {
|
|
2856
2860
|
const criteriaValue = toMatrix(args[k])[i][j].value;
|
|
2857
2861
|
const criterion = predicates[k / 2];
|
|
2858
|
-
validatedPredicates = evaluatePredicate(criteriaValue ?? undefined, criterion);
|
|
2862
|
+
validatedPredicates = evaluatePredicate(criteriaValue ?? undefined, criterion, locale);
|
|
2859
2863
|
if (!validatedPredicates) {
|
|
2860
2864
|
break;
|
|
2861
2865
|
}
|
|
@@ -6163,6 +6167,132 @@ function countUnique(args) {
|
|
|
6163
6167
|
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
6164
6168
|
}
|
|
6165
6169
|
|
|
6170
|
+
function getUnitMatrix(n) {
|
|
6171
|
+
const matrix = Array(n);
|
|
6172
|
+
for (let i = 0; i < n; i++) {
|
|
6173
|
+
matrix[i] = Array(n).fill(0);
|
|
6174
|
+
matrix[i][i] = 1;
|
|
6175
|
+
}
|
|
6176
|
+
return matrix;
|
|
6177
|
+
}
|
|
6178
|
+
/**
|
|
6179
|
+
* Invert a matrix and compute its determinant using Gaussian Elimination.
|
|
6180
|
+
*
|
|
6181
|
+
* The Matrix should be a square matrix, and should be indexed [col][row] instead of the
|
|
6182
|
+
* standard mathematical indexing [row][col].
|
|
6183
|
+
*/
|
|
6184
|
+
function invertMatrix(M) {
|
|
6185
|
+
// Use Gaussian Elimination to calculate the inverse:
|
|
6186
|
+
// (1) 'augment' the matrix (left) by the identity (on the right)
|
|
6187
|
+
// (2) Turn the matrix on the left into the identity using elementary row operations
|
|
6188
|
+
// (3) The matrix on the right becomes the inverse (was the identity matrix)
|
|
6189
|
+
//
|
|
6190
|
+
// There are 3 elementary row operations:
|
|
6191
|
+
// (a) Swap 2 rows. This multiply the determinant by -1.
|
|
6192
|
+
// (b) Multiply a row by a scalar. This multiply the determinant by that scalar.
|
|
6193
|
+
// (c) Add to a row a multiple of another row. This does not change the determinant.
|
|
6194
|
+
if (M.length !== M[0].length) {
|
|
6195
|
+
throw new EvaluationError(_t("Function [[FUNCTION_NAME]] invert matrix error, only square matrices are invertible"));
|
|
6196
|
+
}
|
|
6197
|
+
let determinant = 1;
|
|
6198
|
+
const dim = M.length;
|
|
6199
|
+
const I = getUnitMatrix(dim);
|
|
6200
|
+
const C = M.map((row) => row.slice());
|
|
6201
|
+
// Perform elementary row operations
|
|
6202
|
+
for (let pivot = 0; pivot < dim; pivot++) {
|
|
6203
|
+
let diagonalElement = C[pivot][pivot];
|
|
6204
|
+
// if we have a 0 on the diagonal we'll need to swap with a lower row
|
|
6205
|
+
if (diagonalElement === 0) {
|
|
6206
|
+
//look through every row below the i'th row
|
|
6207
|
+
for (let row = pivot + 1; row < dim; row++) {
|
|
6208
|
+
//if the ii'th row has a non-0 in the i'th col, swap it with that row
|
|
6209
|
+
if (C[pivot][row] != 0) {
|
|
6210
|
+
swapMatrixRows(C, pivot, row);
|
|
6211
|
+
swapMatrixRows(I, pivot, row);
|
|
6212
|
+
determinant *= -1;
|
|
6213
|
+
break;
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
diagonalElement = C[pivot][pivot];
|
|
6217
|
+
//if it's still 0, matrix isn't invertible
|
|
6218
|
+
if (diagonalElement === 0) {
|
|
6219
|
+
return { determinant: 0 };
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
// Scale this row down by e (so we have a 1 on the diagonal)
|
|
6223
|
+
for (let col = 0; col < dim; col++) {
|
|
6224
|
+
C[col][pivot] = C[col][pivot] / diagonalElement;
|
|
6225
|
+
I[col][pivot] = I[col][pivot] / diagonalElement;
|
|
6226
|
+
}
|
|
6227
|
+
determinant *= diagonalElement;
|
|
6228
|
+
// Subtract a multiple of the current row from ALL of
|
|
6229
|
+
// the other rows so that there will be 0's in this column in the
|
|
6230
|
+
// rows above and below this one
|
|
6231
|
+
for (let row = 0; row < dim; row++) {
|
|
6232
|
+
if (row === pivot) {
|
|
6233
|
+
continue;
|
|
6234
|
+
}
|
|
6235
|
+
// We want to change this element to 0
|
|
6236
|
+
const e = C[pivot][row];
|
|
6237
|
+
// Subtract (the row above(or below) scaled by e) from (the
|
|
6238
|
+
// current row) but start at the i'th column and assume all the
|
|
6239
|
+
// stuff left of diagonal is 0 (which it should be if we made this
|
|
6240
|
+
// algorithm correctly)
|
|
6241
|
+
for (let col = 0; col < dim; col++) {
|
|
6242
|
+
C[col][row] -= e * C[col][pivot];
|
|
6243
|
+
I[col][row] -= e * I[col][pivot];
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
// We've done all operations, C should be the identity matrix I should be the inverse
|
|
6248
|
+
return { inverted: I, determinant };
|
|
6249
|
+
}
|
|
6250
|
+
function swapMatrixRows(matrix, row1, row2) {
|
|
6251
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
6252
|
+
const tmp = matrix[i][row1];
|
|
6253
|
+
matrix[i][row1] = matrix[i][row2];
|
|
6254
|
+
matrix[i][row2] = tmp;
|
|
6255
|
+
}
|
|
6256
|
+
}
|
|
6257
|
+
/**
|
|
6258
|
+
* Matrix multiplication of 2 matrices.
|
|
6259
|
+
* ex: matrix1 : n x l, matrix2 : m x n => result : m x l
|
|
6260
|
+
*
|
|
6261
|
+
* Note: we use indexing [col][row] instead of the standard mathematical notation [row][col]
|
|
6262
|
+
*/
|
|
6263
|
+
function multiplyMatrices(matrix1, matrix2) {
|
|
6264
|
+
if (matrix1.length !== matrix2[0].length) {
|
|
6265
|
+
throw new EvaluationError(_t("Cannot multiply matrices : incompatible matrices size."));
|
|
6266
|
+
}
|
|
6267
|
+
const rowsM1 = matrix1[0].length;
|
|
6268
|
+
const colsM2 = matrix2.length;
|
|
6269
|
+
const n = matrix1.length;
|
|
6270
|
+
const result = Array(colsM2);
|
|
6271
|
+
for (let col = 0; col < colsM2; col++) {
|
|
6272
|
+
result[col] = Array(rowsM1);
|
|
6273
|
+
for (let row = 0; row < rowsM1; row++) {
|
|
6274
|
+
let sum = 0;
|
|
6275
|
+
for (let k = 0; k < n; k++) {
|
|
6276
|
+
sum += matrix1[k][row] * matrix2[col][k];
|
|
6277
|
+
}
|
|
6278
|
+
result[col][row] = sum;
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
return result;
|
|
6282
|
+
}
|
|
6283
|
+
/**
|
|
6284
|
+
* Return the input if it's a scalar or the first element of the input if it's a matrix.
|
|
6285
|
+
*/
|
|
6286
|
+
function toScalar(matrix) {
|
|
6287
|
+
if (!isMatrix(matrix)) {
|
|
6288
|
+
return matrix;
|
|
6289
|
+
}
|
|
6290
|
+
if (matrix.length !== 1 || matrix[0].length !== 1) {
|
|
6291
|
+
throw new EvaluationError(_t("The value should be a scalar or a 1x1 matrix"));
|
|
6292
|
+
}
|
|
6293
|
+
return matrix[0][0];
|
|
6294
|
+
}
|
|
6295
|
+
|
|
6166
6296
|
function assertSameNumberOfElements(...args) {
|
|
6167
6297
|
const dims = args[0].length;
|
|
6168
6298
|
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())));
|
|
@@ -6209,6 +6339,161 @@ function min(values, locale) {
|
|
|
6209
6339
|
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
|
|
6210
6340
|
return result === Infinity ? 0 : result;
|
|
6211
6341
|
}
|
|
6342
|
+
function prepareDataForRegression(X, Y, newX) {
|
|
6343
|
+
const _X = X[0].length ? X : [range(1, Y.flat().length + 1)];
|
|
6344
|
+
const nVar = _X.length;
|
|
6345
|
+
let _newX = newX[0].length ? newX : _X;
|
|
6346
|
+
_newX = _newX.length === nVar ? transposeMatrix(_newX) : _newX;
|
|
6347
|
+
return { _X, _newX };
|
|
6348
|
+
}
|
|
6349
|
+
/*
|
|
6350
|
+
* This function performs a linear regression on the data set. It returns an array with two elements.
|
|
6351
|
+
* The first element is the slope, and the second element is the intercept.
|
|
6352
|
+
* The linear regression line is: y = slope*x + intercept
|
|
6353
|
+
* The function use the least squares method to find the best fit for the data set :
|
|
6354
|
+
* see https://www.mathsisfun.com/data/least-squares-regression.html
|
|
6355
|
+
* https://www.statology.org/standard-error-of-estimate/
|
|
6356
|
+
* https://agronomy4future.org/?p=16670
|
|
6357
|
+
* https://vitalflux.com/interpreting-f-statistics-in-linear-regression-formula-examples/
|
|
6358
|
+
* https://web.ist.utl.pt/~ist11038/compute/errtheory/,regression/regrthroughorigin.pdf
|
|
6359
|
+
*/
|
|
6360
|
+
function fullLinearRegression(X, Y, computeIntercept = true, verbose = false) {
|
|
6361
|
+
const y = Y.flat();
|
|
6362
|
+
const n = y.length;
|
|
6363
|
+
let { _X } = prepareDataForRegression(X, Y, [[]]);
|
|
6364
|
+
_X = _X.length === n ? transposeMatrix(_X) : _X.slice();
|
|
6365
|
+
assertSameNumberOfElements(_X[0], y);
|
|
6366
|
+
const nVar = _X.length;
|
|
6367
|
+
const nDeg = n - nVar - (computeIntercept ? 1 : 0);
|
|
6368
|
+
const yMatrix = [y];
|
|
6369
|
+
const xMatrix = transposeMatrix(_X.reverse());
|
|
6370
|
+
let avgX = [];
|
|
6371
|
+
for (let i = 0; i < nVar; i++) {
|
|
6372
|
+
avgX.push(0);
|
|
6373
|
+
if (computeIntercept) {
|
|
6374
|
+
for (const xij of _X[i]) {
|
|
6375
|
+
avgX[i] += xij;
|
|
6376
|
+
}
|
|
6377
|
+
avgX[i] /= n;
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
let avgY = 0;
|
|
6381
|
+
if (computeIntercept) {
|
|
6382
|
+
for (const yi of y) {
|
|
6383
|
+
avgY += yi;
|
|
6384
|
+
}
|
|
6385
|
+
avgY /= n;
|
|
6386
|
+
}
|
|
6387
|
+
const redX = xMatrix.map((row) => row.map((value, i) => value - avgX[i]));
|
|
6388
|
+
if (computeIntercept) {
|
|
6389
|
+
xMatrix.forEach((row) => row.push(1));
|
|
6390
|
+
}
|
|
6391
|
+
const coeffs = getLMSCoefficients(xMatrix, yMatrix);
|
|
6392
|
+
if (!computeIntercept) {
|
|
6393
|
+
coeffs.push([0]);
|
|
6394
|
+
}
|
|
6395
|
+
if (!verbose) {
|
|
6396
|
+
return coeffs;
|
|
6397
|
+
}
|
|
6398
|
+
const dot1 = multiplyMatrices(redX, transposeMatrix(redX));
|
|
6399
|
+
const { inverted: dotInv } = invertMatrix(dot1);
|
|
6400
|
+
if (dotInv === undefined) {
|
|
6401
|
+
throw new EvaluationError(_t("Matrix is not invertible"));
|
|
6402
|
+
}
|
|
6403
|
+
let SSE = 0, SSR = 0;
|
|
6404
|
+
for (let i = 0; i < n; i++) {
|
|
6405
|
+
const yi = y[i] - avgY;
|
|
6406
|
+
let temp = 0;
|
|
6407
|
+
for (let j = 0; j < nVar; j++) {
|
|
6408
|
+
const xi = redX[i][j];
|
|
6409
|
+
temp += xi * coeffs[j][0];
|
|
6410
|
+
}
|
|
6411
|
+
const ei = yi - temp;
|
|
6412
|
+
SSE += ei * ei;
|
|
6413
|
+
SSR += temp * temp;
|
|
6414
|
+
}
|
|
6415
|
+
const RMSE = Math.sqrt(SSE / nDeg);
|
|
6416
|
+
const r2 = SSR / (SSR + SSE);
|
|
6417
|
+
const f_stat = SSR / nVar / (SSE / nDeg);
|
|
6418
|
+
const deltaCoeffs = [];
|
|
6419
|
+
for (let i = 0; i < nVar; i++) {
|
|
6420
|
+
deltaCoeffs.push(RMSE * Math.sqrt(dotInv[i][i]));
|
|
6421
|
+
}
|
|
6422
|
+
if (computeIntercept) {
|
|
6423
|
+
const dot2 = multiplyMatrices(dotInv, [avgX]);
|
|
6424
|
+
const dot3 = multiplyMatrices(transposeMatrix([avgX]), dot2);
|
|
6425
|
+
deltaCoeffs.push(RMSE * Math.sqrt(dot3[0][0] + 1 / y.length));
|
|
6426
|
+
}
|
|
6427
|
+
const returned = [
|
|
6428
|
+
[coeffs[0][0], deltaCoeffs[0], r2, f_stat, SSR],
|
|
6429
|
+
[coeffs[1][0], deltaCoeffs[1], RMSE, nDeg, SSE],
|
|
6430
|
+
];
|
|
6431
|
+
for (let i = 2; i < nVar; i++) {
|
|
6432
|
+
returned.push([coeffs[i][0], deltaCoeffs[i], "", "", ""]);
|
|
6433
|
+
}
|
|
6434
|
+
if (computeIntercept) {
|
|
6435
|
+
returned.push([coeffs[nVar][0], deltaCoeffs[nVar], "", "", ""]);
|
|
6436
|
+
}
|
|
6437
|
+
else {
|
|
6438
|
+
returned.push([0, "", "", "", ""]);
|
|
6439
|
+
}
|
|
6440
|
+
return returned;
|
|
6441
|
+
}
|
|
6442
|
+
/*
|
|
6443
|
+
This function performs a polynomial regression on the data set. It returns the coefficients of
|
|
6444
|
+
the polynomial function that best fits the data set.
|
|
6445
|
+
The polynomial function is: y = c0 + c1*x + c2*x^2 + ... + cn*x^n, where n is the order (degree)
|
|
6446
|
+
of the polynomial. The returned coefficients are then in the form: [c0, c1, c2, ..., cn]
|
|
6447
|
+
The function is based on the method of least squares :
|
|
6448
|
+
see: https://mathworld.wolfram.com/LeastSquaresFittingPolynomial.html
|
|
6449
|
+
*/
|
|
6450
|
+
function polynomialRegression(flatY, flatX, order, intercept) {
|
|
6451
|
+
assertSameNumberOfElements(flatX, flatY);
|
|
6452
|
+
assert(() => order >= 1, _t("Function [[FUNCTION_NAME]] A regression of order less than 1 cannot be possible."));
|
|
6453
|
+
const yMatrix = [flatY];
|
|
6454
|
+
const xMatrix = flatX.map((x) => range(0, order).map((i) => Math.pow(x, order - i)));
|
|
6455
|
+
if (intercept) {
|
|
6456
|
+
xMatrix.forEach((row) => row.push(1));
|
|
6457
|
+
}
|
|
6458
|
+
const coeffs = getLMSCoefficients(xMatrix, yMatrix);
|
|
6459
|
+
if (!intercept) {
|
|
6460
|
+
coeffs.push([0]);
|
|
6461
|
+
}
|
|
6462
|
+
return coeffs;
|
|
6463
|
+
}
|
|
6464
|
+
function getLMSCoefficients(xMatrix, yMatrix) {
|
|
6465
|
+
const xMatrixT = transposeMatrix(xMatrix);
|
|
6466
|
+
const dot1 = multiplyMatrices(xMatrix, xMatrixT);
|
|
6467
|
+
const { inverted: dotInv } = invertMatrix(dot1);
|
|
6468
|
+
if (dotInv === undefined) {
|
|
6469
|
+
throw new EvaluationError(_t("Matrix is not invertible"));
|
|
6470
|
+
}
|
|
6471
|
+
const dot2 = multiplyMatrices(xMatrix, yMatrix);
|
|
6472
|
+
return transposeMatrix(multiplyMatrices(dotInv, dot2));
|
|
6473
|
+
}
|
|
6474
|
+
function evaluatePolynomial(coeffs, x, order) {
|
|
6475
|
+
return coeffs.reduce((acc, coeff, i) => acc + coeff * Math.pow(x, order - i), 0);
|
|
6476
|
+
}
|
|
6477
|
+
function expM(M) {
|
|
6478
|
+
return M.map((col) => col.map((cell) => Math.exp(cell)));
|
|
6479
|
+
}
|
|
6480
|
+
function logM(M) {
|
|
6481
|
+
return M.map((col) => col.map((cell) => Math.log(cell)));
|
|
6482
|
+
}
|
|
6483
|
+
function predictLinearValues(Y, X, newX, computeIntercept) {
|
|
6484
|
+
const { _X, _newX } = prepareDataForRegression(X, Y, newX);
|
|
6485
|
+
const coeffs = fullLinearRegression(_X, Y, computeIntercept, false);
|
|
6486
|
+
const nVar = coeffs.length - 1;
|
|
6487
|
+
const newY = _newX.map((col) => {
|
|
6488
|
+
let value = 0;
|
|
6489
|
+
for (let i = 0; i < nVar; i++) {
|
|
6490
|
+
value += coeffs[i][0] * col[nVar - i - 1];
|
|
6491
|
+
}
|
|
6492
|
+
value += coeffs[nVar][0];
|
|
6493
|
+
return [value];
|
|
6494
|
+
});
|
|
6495
|
+
return newY.length === newX.length ? newY : transposeMatrix(newY);
|
|
6496
|
+
}
|
|
6212
6497
|
|
|
6213
6498
|
const pivotTimeAdapterRegistry = new Registry();
|
|
6214
6499
|
function pivotTimeAdapter(granularity) {
|
|
@@ -6546,6 +6831,9 @@ function toNormalizedPivotValue(dimension, groupValue) {
|
|
|
6546
6831
|
const groupValueString = typeof groupValue === "boolean"
|
|
6547
6832
|
? toString(groupValue).toLocaleLowerCase()
|
|
6548
6833
|
: toString(groupValue);
|
|
6834
|
+
if (groupValueString === "null") {
|
|
6835
|
+
return null;
|
|
6836
|
+
}
|
|
6549
6837
|
if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
|
|
6550
6838
|
throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
|
|
6551
6839
|
field: dimension.displayName,
|
|
@@ -6566,6 +6854,9 @@ function normalizeDateTime(value, granularity) {
|
|
|
6566
6854
|
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
6567
6855
|
}
|
|
6568
6856
|
function toFunctionPivotValue(value, dimension) {
|
|
6857
|
+
if (value === null) {
|
|
6858
|
+
return `"null"`;
|
|
6859
|
+
}
|
|
6569
6860
|
if (!pivotToFunctionValueRegistry.contains(dimension.type)) {
|
|
6570
6861
|
return `"${value}"`;
|
|
6571
6862
|
}
|
|
@@ -7767,6 +8058,7 @@ class ComposerFocusStore extends SpreadsheetStore {
|
|
|
7767
8058
|
}
|
|
7768
8059
|
}
|
|
7769
8060
|
|
|
8061
|
+
const TREND_LINE_XAXIS_ID = "x1";
|
|
7770
8062
|
/**
|
|
7771
8063
|
* This file contains helpers that are common to different charts (mainly
|
|
7772
8064
|
* line, bar and pie charts)
|
|
@@ -8088,6 +8380,84 @@ function getDefinedAxis(definition) {
|
|
|
8088
8380
|
useLeftAxis ||= !useRightAxis;
|
|
8089
8381
|
return { useLeftAxis, useRightAxis };
|
|
8090
8382
|
}
|
|
8383
|
+
function computeChartPadding({ displayTitle, displayLegend, }) {
|
|
8384
|
+
let top = 25;
|
|
8385
|
+
if (displayTitle) {
|
|
8386
|
+
top = 0;
|
|
8387
|
+
}
|
|
8388
|
+
else if (displayLegend) {
|
|
8389
|
+
top = 10;
|
|
8390
|
+
}
|
|
8391
|
+
return { left: 20, right: 20, top, bottom: 10 };
|
|
8392
|
+
}
|
|
8393
|
+
function getTrendDatasetForBarChart(config, dataset) {
|
|
8394
|
+
const filteredValues = [];
|
|
8395
|
+
const filteredLabels = [];
|
|
8396
|
+
const labels = [];
|
|
8397
|
+
for (let i = 0; i < dataset.data.length; i++) {
|
|
8398
|
+
if (dataset.data[i] !== null) {
|
|
8399
|
+
filteredValues.push(dataset.data[i]);
|
|
8400
|
+
filteredLabels.push(i + 1);
|
|
8401
|
+
}
|
|
8402
|
+
labels.push(i + 1);
|
|
8403
|
+
}
|
|
8404
|
+
const newLabels = range(0.5, labels.length + 0.55, 0.2);
|
|
8405
|
+
const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
|
|
8406
|
+
if (!newValues.length) {
|
|
8407
|
+
return;
|
|
8408
|
+
}
|
|
8409
|
+
return getFullTrendingLineDataSet(dataset, config, newValues);
|
|
8410
|
+
}
|
|
8411
|
+
function getFullTrendingLineDataSet(dataset, config, data) {
|
|
8412
|
+
const backgroundColor = config.color ?? lightenColor(dataset.backgroundColor, 0.5);
|
|
8413
|
+
return {
|
|
8414
|
+
...dataset,
|
|
8415
|
+
type: "line",
|
|
8416
|
+
xAxisID: TREND_LINE_XAXIS_ID,
|
|
8417
|
+
label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
|
|
8418
|
+
data,
|
|
8419
|
+
order: -1,
|
|
8420
|
+
showLine: true,
|
|
8421
|
+
pointRadius: 0,
|
|
8422
|
+
backgroundColor,
|
|
8423
|
+
borderColor: backgroundColor,
|
|
8424
|
+
borderDash: [5, 5],
|
|
8425
|
+
};
|
|
8426
|
+
}
|
|
8427
|
+
function interpolateData(config, values, labels, newLabels) {
|
|
8428
|
+
if (values.length === 0 || labels.length === 0 || newLabels.length === 0) {
|
|
8429
|
+
return [];
|
|
8430
|
+
}
|
|
8431
|
+
switch (config.type) {
|
|
8432
|
+
case "polynomial": {
|
|
8433
|
+
const order = config.order ?? 2;
|
|
8434
|
+
if (order === 1) {
|
|
8435
|
+
return predictLinearValues([values], [labels], [newLabels], true)[0];
|
|
8436
|
+
}
|
|
8437
|
+
const coeffs = polynomialRegression(values, labels, order, true).flat();
|
|
8438
|
+
return newLabels.map((v) => evaluatePolynomial(coeffs, v, order));
|
|
8439
|
+
}
|
|
8440
|
+
case "exponential": {
|
|
8441
|
+
const positiveLogValues = [];
|
|
8442
|
+
const filteredLabels = [];
|
|
8443
|
+
for (let i = 0; i < values.length; i++) {
|
|
8444
|
+
if (values[i] > 0) {
|
|
8445
|
+
positiveLogValues.push(Math.log(values[i]));
|
|
8446
|
+
filteredLabels.push(labels[i]);
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
if (!filteredLabels.length) {
|
|
8450
|
+
return [];
|
|
8451
|
+
}
|
|
8452
|
+
return expM(predictLinearValues([positiveLogValues], [filteredLabels], [newLabels], true))[0];
|
|
8453
|
+
}
|
|
8454
|
+
case "logarithmic": {
|
|
8455
|
+
return predictLinearValues([values], logM([labels]), logM([newLabels]), true)[0];
|
|
8456
|
+
}
|
|
8457
|
+
default:
|
|
8458
|
+
return [];
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8091
8461
|
|
|
8092
8462
|
/** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
|
|
8093
8463
|
const chartShowValuesPlugin = {
|
|
@@ -9217,132 +9587,6 @@ function assertSquareMatrix(errorStr, arg) {
|
|
|
9217
9587
|
assert(() => arg.length === arg[0].length, errorStr);
|
|
9218
9588
|
}
|
|
9219
9589
|
|
|
9220
|
-
function getUnitMatrix(n) {
|
|
9221
|
-
const matrix = Array(n);
|
|
9222
|
-
for (let i = 0; i < n; i++) {
|
|
9223
|
-
matrix[i] = Array(n).fill(0);
|
|
9224
|
-
matrix[i][i] = 1;
|
|
9225
|
-
}
|
|
9226
|
-
return matrix;
|
|
9227
|
-
}
|
|
9228
|
-
/**
|
|
9229
|
-
* Invert a matrix and compute its determinant using Gaussian Elimination.
|
|
9230
|
-
*
|
|
9231
|
-
* The Matrix should be a square matrix, and should be indexed [col][row] instead of the
|
|
9232
|
-
* standard mathematical indexing [row][col].
|
|
9233
|
-
*/
|
|
9234
|
-
function invertMatrix(M) {
|
|
9235
|
-
// Use Gaussian Elimination to calculate the inverse:
|
|
9236
|
-
// (1) 'augment' the matrix (left) by the identity (on the right)
|
|
9237
|
-
// (2) Turn the matrix on the left into the identity using elementary row operations
|
|
9238
|
-
// (3) The matrix on the right becomes the inverse (was the identity matrix)
|
|
9239
|
-
//
|
|
9240
|
-
// There are 3 elementary row operations:
|
|
9241
|
-
// (a) Swap 2 rows. This multiply the determinant by -1.
|
|
9242
|
-
// (b) Multiply a row by a scalar. This multiply the determinant by that scalar.
|
|
9243
|
-
// (c) Add to a row a multiple of another row. This does not change the determinant.
|
|
9244
|
-
if (M.length !== M[0].length) {
|
|
9245
|
-
throw new EvaluationError(_t("Function [[FUNCTION_NAME]] invert matrix error, only square matrices are invertible"));
|
|
9246
|
-
}
|
|
9247
|
-
let determinant = 1;
|
|
9248
|
-
const dim = M.length;
|
|
9249
|
-
const I = getUnitMatrix(dim);
|
|
9250
|
-
const C = M.map((row) => row.slice());
|
|
9251
|
-
// Perform elementary row operations
|
|
9252
|
-
for (let pivot = 0; pivot < dim; pivot++) {
|
|
9253
|
-
let diagonalElement = C[pivot][pivot];
|
|
9254
|
-
// if we have a 0 on the diagonal we'll need to swap with a lower row
|
|
9255
|
-
if (diagonalElement === 0) {
|
|
9256
|
-
//look through every row below the i'th row
|
|
9257
|
-
for (let row = pivot + 1; row < dim; row++) {
|
|
9258
|
-
//if the ii'th row has a non-0 in the i'th col, swap it with that row
|
|
9259
|
-
if (C[pivot][row] != 0) {
|
|
9260
|
-
swapMatrixRows(C, pivot, row);
|
|
9261
|
-
swapMatrixRows(I, pivot, row);
|
|
9262
|
-
determinant *= -1;
|
|
9263
|
-
break;
|
|
9264
|
-
}
|
|
9265
|
-
}
|
|
9266
|
-
diagonalElement = C[pivot][pivot];
|
|
9267
|
-
//if it's still 0, matrix isn't invertible
|
|
9268
|
-
if (diagonalElement === 0) {
|
|
9269
|
-
return { determinant: 0 };
|
|
9270
|
-
}
|
|
9271
|
-
}
|
|
9272
|
-
// Scale this row down by e (so we have a 1 on the diagonal)
|
|
9273
|
-
for (let col = 0; col < dim; col++) {
|
|
9274
|
-
C[col][pivot] = C[col][pivot] / diagonalElement;
|
|
9275
|
-
I[col][pivot] = I[col][pivot] / diagonalElement;
|
|
9276
|
-
}
|
|
9277
|
-
determinant *= diagonalElement;
|
|
9278
|
-
// Subtract a multiple of the current row from ALL of
|
|
9279
|
-
// the other rows so that there will be 0's in this column in the
|
|
9280
|
-
// rows above and below this one
|
|
9281
|
-
for (let row = 0; row < dim; row++) {
|
|
9282
|
-
if (row === pivot) {
|
|
9283
|
-
continue;
|
|
9284
|
-
}
|
|
9285
|
-
// We want to change this element to 0
|
|
9286
|
-
const e = C[pivot][row];
|
|
9287
|
-
// Subtract (the row above(or below) scaled by e) from (the
|
|
9288
|
-
// current row) but start at the i'th column and assume all the
|
|
9289
|
-
// stuff left of diagonal is 0 (which it should be if we made this
|
|
9290
|
-
// algorithm correctly)
|
|
9291
|
-
for (let col = 0; col < dim; col++) {
|
|
9292
|
-
C[col][row] -= e * C[col][pivot];
|
|
9293
|
-
I[col][row] -= e * I[col][pivot];
|
|
9294
|
-
}
|
|
9295
|
-
}
|
|
9296
|
-
}
|
|
9297
|
-
// We've done all operations, C should be the identity matrix I should be the inverse
|
|
9298
|
-
return { inverted: I, determinant };
|
|
9299
|
-
}
|
|
9300
|
-
function swapMatrixRows(matrix, row1, row2) {
|
|
9301
|
-
for (let i = 0; i < matrix.length; i++) {
|
|
9302
|
-
const tmp = matrix[i][row1];
|
|
9303
|
-
matrix[i][row1] = matrix[i][row2];
|
|
9304
|
-
matrix[i][row2] = tmp;
|
|
9305
|
-
}
|
|
9306
|
-
}
|
|
9307
|
-
/**
|
|
9308
|
-
* Matrix multiplication of 2 matrices.
|
|
9309
|
-
* ex: matrix1 : n x l, matrix2 : m x n => result : m x l
|
|
9310
|
-
*
|
|
9311
|
-
* Note: we use indexing [col][row] instead of the standard mathematical notation [row][col]
|
|
9312
|
-
*/
|
|
9313
|
-
function multiplyMatrices(matrix1, matrix2) {
|
|
9314
|
-
if (matrix1.length !== matrix2[0].length) {
|
|
9315
|
-
throw new EvaluationError(_t("Cannot multiply matrices : incompatible matrices size."));
|
|
9316
|
-
}
|
|
9317
|
-
const rowsM1 = matrix1[0].length;
|
|
9318
|
-
const colsM2 = matrix2.length;
|
|
9319
|
-
const n = matrix1.length;
|
|
9320
|
-
const result = Array(colsM2);
|
|
9321
|
-
for (let col = 0; col < colsM2; col++) {
|
|
9322
|
-
result[col] = Array(rowsM1);
|
|
9323
|
-
for (let row = 0; row < rowsM1; row++) {
|
|
9324
|
-
let sum = 0;
|
|
9325
|
-
for (let k = 0; k < n; k++) {
|
|
9326
|
-
sum += matrix1[k][row] * matrix2[col][k];
|
|
9327
|
-
}
|
|
9328
|
-
result[col][row] = sum;
|
|
9329
|
-
}
|
|
9330
|
-
}
|
|
9331
|
-
return result;
|
|
9332
|
-
}
|
|
9333
|
-
/**
|
|
9334
|
-
* Return the input if it's a scalar or the first element of the input if it's a matrix.
|
|
9335
|
-
*/
|
|
9336
|
-
function toScalar(matrix) {
|
|
9337
|
-
if (!isMatrix(matrix)) {
|
|
9338
|
-
return matrix;
|
|
9339
|
-
}
|
|
9340
|
-
if (matrix.length !== 1 || matrix[0].length !== 1) {
|
|
9341
|
-
throw new EvaluationError(_t("The value should be a scalar or a 1x1 matrix"));
|
|
9342
|
-
}
|
|
9343
|
-
return matrix[0][0];
|
|
9344
|
-
}
|
|
9345
|
-
|
|
9346
9590
|
// -----------------------------------------------------------------------------
|
|
9347
9591
|
// ARRAY_CONSTRAIN
|
|
9348
9592
|
// -----------------------------------------------------------------------------
|
|
@@ -11073,161 +11317,6 @@ function centile(data, percent, isInclusive, locale) {
|
|
|
11073
11317
|
}
|
|
11074
11318
|
return percentile(sortedArray, _percent, isInclusive);
|
|
11075
11319
|
}
|
|
11076
|
-
function prepareDataForRegression(X, Y, newX) {
|
|
11077
|
-
const _X = X[0].length ? X : [range(1, Y.flat().length + 1)];
|
|
11078
|
-
const nVar = _X.length;
|
|
11079
|
-
let _newX = newX[0].length ? newX : _X;
|
|
11080
|
-
_newX = _newX.length === nVar ? transposeMatrix(_newX) : _newX;
|
|
11081
|
-
return { _X, _newX };
|
|
11082
|
-
}
|
|
11083
|
-
/*
|
|
11084
|
-
* This function performs a linear regression on the data set. It returns an array with two elements.
|
|
11085
|
-
* The first element is the slope, and the second element is the intercept.
|
|
11086
|
-
* The linear regression line is: y = slope*x + intercept
|
|
11087
|
-
* The function use the least squares method to find the best fit for the data set :
|
|
11088
|
-
* see https://www.mathsisfun.com/data/least-squares-regression.html
|
|
11089
|
-
* https://www.statology.org/standard-error-of-estimate/
|
|
11090
|
-
* https://agronomy4future.org/?p=16670
|
|
11091
|
-
* https://vitalflux.com/interpreting-f-statistics-in-linear-regression-formula-examples/
|
|
11092
|
-
* https://web.ist.utl.pt/~ist11038/compute/errtheory/,regression/regrthroughorigin.pdf
|
|
11093
|
-
*/
|
|
11094
|
-
function fullLinearRegression(X, Y, computeIntercept = true, verbose = false) {
|
|
11095
|
-
const y = Y.flat();
|
|
11096
|
-
const n = y.length;
|
|
11097
|
-
let { _X } = prepareDataForRegression(X, Y, [[]]);
|
|
11098
|
-
_X = _X.length === n ? transposeMatrix(_X) : _X.slice();
|
|
11099
|
-
assertSameNumberOfElements(_X[0], y);
|
|
11100
|
-
const nVar = _X.length;
|
|
11101
|
-
const nDeg = n - nVar - (computeIntercept ? 1 : 0);
|
|
11102
|
-
const yMatrix = [y];
|
|
11103
|
-
const xMatrix = transposeMatrix(_X.reverse());
|
|
11104
|
-
let avgX = [];
|
|
11105
|
-
for (let i = 0; i < nVar; i++) {
|
|
11106
|
-
avgX.push(0);
|
|
11107
|
-
if (computeIntercept) {
|
|
11108
|
-
for (const xij of _X[i]) {
|
|
11109
|
-
avgX[i] += xij;
|
|
11110
|
-
}
|
|
11111
|
-
avgX[i] /= n;
|
|
11112
|
-
}
|
|
11113
|
-
}
|
|
11114
|
-
let avgY = 0;
|
|
11115
|
-
if (computeIntercept) {
|
|
11116
|
-
for (const yi of y) {
|
|
11117
|
-
avgY += yi;
|
|
11118
|
-
}
|
|
11119
|
-
avgY /= n;
|
|
11120
|
-
}
|
|
11121
|
-
const redX = xMatrix.map((row) => row.map((value, i) => value - avgX[i]));
|
|
11122
|
-
if (computeIntercept) {
|
|
11123
|
-
xMatrix.forEach((row) => row.push(1));
|
|
11124
|
-
}
|
|
11125
|
-
const coeffs = getLMSCoefficients(xMatrix, yMatrix);
|
|
11126
|
-
if (!computeIntercept) {
|
|
11127
|
-
coeffs.push([0]);
|
|
11128
|
-
}
|
|
11129
|
-
if (!verbose) {
|
|
11130
|
-
return coeffs;
|
|
11131
|
-
}
|
|
11132
|
-
const dot1 = multiplyMatrices(redX, transposeMatrix(redX));
|
|
11133
|
-
const { inverted: dotInv } = invertMatrix(dot1);
|
|
11134
|
-
if (dotInv === undefined) {
|
|
11135
|
-
throw new EvaluationError(_t("Matrix is not invertible"));
|
|
11136
|
-
}
|
|
11137
|
-
let SSE = 0, SSR = 0;
|
|
11138
|
-
for (let i = 0; i < n; i++) {
|
|
11139
|
-
const yi = y[i] - avgY;
|
|
11140
|
-
let temp = 0;
|
|
11141
|
-
for (let j = 0; j < nVar; j++) {
|
|
11142
|
-
const xi = redX[i][j];
|
|
11143
|
-
temp += xi * coeffs[j][0];
|
|
11144
|
-
}
|
|
11145
|
-
const ei = yi - temp;
|
|
11146
|
-
SSE += ei * ei;
|
|
11147
|
-
SSR += temp * temp;
|
|
11148
|
-
}
|
|
11149
|
-
const RMSE = Math.sqrt(SSE / nDeg);
|
|
11150
|
-
const r2 = SSR / (SSR + SSE);
|
|
11151
|
-
const f_stat = SSR / nVar / (SSE / nDeg);
|
|
11152
|
-
const deltaCoeffs = [];
|
|
11153
|
-
for (let i = 0; i < nVar; i++) {
|
|
11154
|
-
deltaCoeffs.push(RMSE * Math.sqrt(dotInv[i][i]));
|
|
11155
|
-
}
|
|
11156
|
-
if (computeIntercept) {
|
|
11157
|
-
const dot2 = multiplyMatrices(dotInv, [avgX]);
|
|
11158
|
-
const dot3 = multiplyMatrices(transposeMatrix([avgX]), dot2);
|
|
11159
|
-
deltaCoeffs.push(RMSE * Math.sqrt(dot3[0][0] + 1 / y.length));
|
|
11160
|
-
}
|
|
11161
|
-
const returned = [
|
|
11162
|
-
[coeffs[0][0], deltaCoeffs[0], r2, f_stat, SSR],
|
|
11163
|
-
[coeffs[1][0], deltaCoeffs[1], RMSE, nDeg, SSE],
|
|
11164
|
-
];
|
|
11165
|
-
for (let i = 2; i < nVar; i++) {
|
|
11166
|
-
returned.push([coeffs[i][0], deltaCoeffs[i], "", "", ""]);
|
|
11167
|
-
}
|
|
11168
|
-
if (computeIntercept) {
|
|
11169
|
-
returned.push([coeffs[nVar][0], deltaCoeffs[nVar], "", "", ""]);
|
|
11170
|
-
}
|
|
11171
|
-
else {
|
|
11172
|
-
returned.push([0, "", "", "", ""]);
|
|
11173
|
-
}
|
|
11174
|
-
return returned;
|
|
11175
|
-
}
|
|
11176
|
-
/*
|
|
11177
|
-
This function performs a polynomial regression on the data set. It returns the coefficients of
|
|
11178
|
-
the polynomial function that best fits the data set.
|
|
11179
|
-
The polynomial function is: y = c0 + c1*x + c2*x^2 + ... + cn*x^n, where n is the order (degree)
|
|
11180
|
-
of the polynomial. The returned coefficients are then in the form: [c0, c1, c2, ..., cn]
|
|
11181
|
-
The function is based on the method of least squares :
|
|
11182
|
-
see: https://mathworld.wolfram.com/LeastSquaresFittingPolynomial.html
|
|
11183
|
-
*/
|
|
11184
|
-
function polynomialRegression(flatY, flatX, order, intercept) {
|
|
11185
|
-
assertSameNumberOfElements(flatX, flatY);
|
|
11186
|
-
assert(() => order >= 1, _t("Function [[FUNCTION_NAME]] A regression of order less than 1 cannot be possible."));
|
|
11187
|
-
const yMatrix = [flatY];
|
|
11188
|
-
const xMatrix = flatX.map((x) => range(0, order).map((i) => Math.pow(x, order - i)));
|
|
11189
|
-
if (intercept) {
|
|
11190
|
-
xMatrix.forEach((row) => row.push(1));
|
|
11191
|
-
}
|
|
11192
|
-
const coeffs = getLMSCoefficients(xMatrix, yMatrix);
|
|
11193
|
-
if (!intercept) {
|
|
11194
|
-
coeffs.push([0]);
|
|
11195
|
-
}
|
|
11196
|
-
return coeffs;
|
|
11197
|
-
}
|
|
11198
|
-
function getLMSCoefficients(xMatrix, yMatrix) {
|
|
11199
|
-
const xMatrixT = transposeMatrix(xMatrix);
|
|
11200
|
-
const dot1 = multiplyMatrices(xMatrix, xMatrixT);
|
|
11201
|
-
const { inverted: dotInv } = invertMatrix(dot1);
|
|
11202
|
-
if (dotInv === undefined) {
|
|
11203
|
-
throw new EvaluationError(_t("Matrix is not invertible"));
|
|
11204
|
-
}
|
|
11205
|
-
const dot2 = multiplyMatrices(xMatrix, yMatrix);
|
|
11206
|
-
return transposeMatrix(multiplyMatrices(dotInv, dot2));
|
|
11207
|
-
}
|
|
11208
|
-
function evaluatePolynomial(coeffs, x, order) {
|
|
11209
|
-
return coeffs.reduce((acc, coeff, i) => acc + coeff * Math.pow(x, order - i), 0);
|
|
11210
|
-
}
|
|
11211
|
-
function expM(M) {
|
|
11212
|
-
return M.map((col) => col.map((cell) => Math.exp(cell)));
|
|
11213
|
-
}
|
|
11214
|
-
function logM(M) {
|
|
11215
|
-
return M.map((col) => col.map((cell) => Math.log(cell)));
|
|
11216
|
-
}
|
|
11217
|
-
function predictLinearValues(Y, X, newX, computeIntercept) {
|
|
11218
|
-
const { _X, _newX } = prepareDataForRegression(X, Y, newX);
|
|
11219
|
-
const coeffs = fullLinearRegression(_X, Y, computeIntercept, false);
|
|
11220
|
-
const nVar = coeffs.length - 1;
|
|
11221
|
-
const newY = _newX.map((col) => {
|
|
11222
|
-
let value = 0;
|
|
11223
|
-
for (let i = 0; i < nVar; i++) {
|
|
11224
|
-
value += coeffs[i][0] * col[nVar - i - 1];
|
|
11225
|
-
}
|
|
11226
|
-
value += coeffs[nVar][0];
|
|
11227
|
-
return [value];
|
|
11228
|
-
});
|
|
11229
|
-
return newY.length === newX.length ? newY : transposeMatrix(newY);
|
|
11230
|
-
}
|
|
11231
11320
|
// -----------------------------------------------------------------------------
|
|
11232
11321
|
// AVEDEV
|
|
11233
11322
|
// -----------------------------------------------------------------------------
|
|
@@ -18979,6 +19068,78 @@ function isCtrlKey(ev) {
|
|
|
18979
19068
|
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
18980
19069
|
}
|
|
18981
19070
|
|
|
19071
|
+
/**
|
|
19072
|
+
* Return the o-spreadsheet element position relative
|
|
19073
|
+
* to the browser viewport.
|
|
19074
|
+
*/
|
|
19075
|
+
function useSpreadsheetRect() {
|
|
19076
|
+
const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
19077
|
+
let spreadsheetElement = null;
|
|
19078
|
+
function updatePosition() {
|
|
19079
|
+
if (!spreadsheetElement) {
|
|
19080
|
+
spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
19081
|
+
}
|
|
19082
|
+
if (spreadsheetElement) {
|
|
19083
|
+
const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
|
|
19084
|
+
position.x = left;
|
|
19085
|
+
position.y = top;
|
|
19086
|
+
position.width = width;
|
|
19087
|
+
position.height = height;
|
|
19088
|
+
}
|
|
19089
|
+
}
|
|
19090
|
+
owl.onMounted(updatePosition);
|
|
19091
|
+
owl.onPatched(updatePosition);
|
|
19092
|
+
return position;
|
|
19093
|
+
}
|
|
19094
|
+
/**
|
|
19095
|
+
* Return the component (or ref's component) BoundingRect, relative
|
|
19096
|
+
* to the upper left corner of the screen (<body> element).
|
|
19097
|
+
*
|
|
19098
|
+
* Note: when used with a <Portal/> component, it will
|
|
19099
|
+
* return the portal position, not the teleported position.
|
|
19100
|
+
*/
|
|
19101
|
+
function useAbsoluteBoundingRect(ref) {
|
|
19102
|
+
const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
19103
|
+
function updateElRect() {
|
|
19104
|
+
const el = ref.el;
|
|
19105
|
+
if (el === null) {
|
|
19106
|
+
return;
|
|
19107
|
+
}
|
|
19108
|
+
const { top, left, width, height } = el.getBoundingClientRect();
|
|
19109
|
+
rect.x = left;
|
|
19110
|
+
rect.y = top;
|
|
19111
|
+
rect.width = width;
|
|
19112
|
+
rect.height = height;
|
|
19113
|
+
}
|
|
19114
|
+
owl.onMounted(updateElRect);
|
|
19115
|
+
owl.onPatched(updateElRect);
|
|
19116
|
+
return rect;
|
|
19117
|
+
}
|
|
19118
|
+
/**
|
|
19119
|
+
* Get the rectangle inside which a popover should stay when being displayed.
|
|
19120
|
+
* It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
|
|
19121
|
+
* element by default.
|
|
19122
|
+
*
|
|
19123
|
+
* Coordinates are expressed expressed as absolute DOM position.
|
|
19124
|
+
*/
|
|
19125
|
+
function usePopoverContainer() {
|
|
19126
|
+
const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
19127
|
+
const component = owl.useComponent();
|
|
19128
|
+
const spreadsheetRect = useSpreadsheetRect();
|
|
19129
|
+
function updateRect() {
|
|
19130
|
+
const env = component.env;
|
|
19131
|
+
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
19132
|
+
container.x = newRect.x;
|
|
19133
|
+
container.y = newRect.y;
|
|
19134
|
+
container.width = newRect.width;
|
|
19135
|
+
container.height = newRect.height;
|
|
19136
|
+
}
|
|
19137
|
+
updateRect();
|
|
19138
|
+
owl.onMounted(updateRect);
|
|
19139
|
+
owl.onPatched(updateRect);
|
|
19140
|
+
return container;
|
|
19141
|
+
}
|
|
19142
|
+
|
|
18982
19143
|
const arrowMap = {
|
|
18983
19144
|
ArrowDown: "down",
|
|
18984
19145
|
ArrowLeft: "left",
|
|
@@ -19577,7 +19738,9 @@ class CellComposer extends owl.Component {
|
|
|
19577
19738
|
argToFocus: 0,
|
|
19578
19739
|
});
|
|
19579
19740
|
compositionActive = false;
|
|
19741
|
+
spreadsheetRect = useSpreadsheetRect();
|
|
19580
19742
|
get assistantStyle() {
|
|
19743
|
+
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
19581
19744
|
const assistantStyle = {};
|
|
19582
19745
|
assistantStyle["min-width"] = `${this.props.rect?.width || ASSISTANT_WIDTH}px`;
|
|
19583
19746
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
@@ -19602,8 +19765,11 @@ class CellComposer extends owl.Component {
|
|
|
19602
19765
|
assistantStyle.right = `0px`;
|
|
19603
19766
|
}
|
|
19604
19767
|
}
|
|
19605
|
-
else
|
|
19606
|
-
assistantStyle["max-height"] = `${this.
|
|
19768
|
+
else {
|
|
19769
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
19770
|
+
if (composerRect.left + ASSISTANT_WIDTH > this.spreadsheetRect.width) {
|
|
19771
|
+
assistantStyle.right = `0px`;
|
|
19772
|
+
}
|
|
19607
19773
|
}
|
|
19608
19774
|
return cssPropertiesToCss(assistantStyle);
|
|
19609
19775
|
}
|
|
@@ -21691,6 +21857,7 @@ const CfTerms = {
|
|
|
21691
21857
|
["ValueUpperInvalidFormula" /* CommandResult.ValueUpperInvalidFormula */]: _t("Invalid upper inflection point formula"),
|
|
21692
21858
|
["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
|
|
21693
21859
|
["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
|
|
21860
|
+
["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
|
|
21694
21861
|
Unexpected: _t("The rule is invalid for an unknown reason"),
|
|
21695
21862
|
},
|
|
21696
21863
|
ColorScale: _t("Color scale"),
|
|
@@ -22224,7 +22391,22 @@ class BarChart extends AbstractChart {
|
|
|
22224
22391
|
return new BarChart(definition, this.sheetId, this.getters);
|
|
22225
22392
|
}
|
|
22226
22393
|
}
|
|
22227
|
-
function
|
|
22394
|
+
function createBarChartRuntime(chart, getters) {
|
|
22395
|
+
const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
|
|
22396
|
+
let labels = labelValues.formattedValues;
|
|
22397
|
+
let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
|
|
22398
|
+
if (chart.dataSetsHaveTitle &&
|
|
22399
|
+
dataSetsValues[0] &&
|
|
22400
|
+
labels.length > dataSetsValues[0].data.length) {
|
|
22401
|
+
labels.shift();
|
|
22402
|
+
}
|
|
22403
|
+
({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
|
|
22404
|
+
if (chart.aggregated) {
|
|
22405
|
+
({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
|
|
22406
|
+
}
|
|
22407
|
+
const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
|
|
22408
|
+
const locale = getters.getLocale();
|
|
22409
|
+
const localeFormat = { format: dataSetFormat, locale };
|
|
22228
22410
|
const fontColor = chartFontColor(chart.background);
|
|
22229
22411
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
|
|
22230
22412
|
...localeFormat,
|
|
@@ -22233,7 +22415,7 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
22233
22415
|
const legend = {
|
|
22234
22416
|
labels: { color: fontColor },
|
|
22235
22417
|
};
|
|
22236
|
-
if (
|
|
22418
|
+
if (chart.legendPosition === "none") {
|
|
22237
22419
|
legend.display = false;
|
|
22238
22420
|
}
|
|
22239
22421
|
else {
|
|
@@ -22241,7 +22423,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
22241
22423
|
}
|
|
22242
22424
|
config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
|
|
22243
22425
|
config.options.layout = {
|
|
22244
|
-
padding: {
|
|
22426
|
+
padding: computeChartPadding({
|
|
22427
|
+
displayTitle: !!chart.title.text,
|
|
22428
|
+
displayLegend: chart.legendPosition === "top",
|
|
22429
|
+
}),
|
|
22245
22430
|
};
|
|
22246
22431
|
config.options.indexAxis = chart.horizontal ? "y" : "x";
|
|
22247
22432
|
const formatCallback = (value) => {
|
|
@@ -22299,27 +22484,11 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
22299
22484
|
horizontal: chart.horizontal,
|
|
22300
22485
|
callback: formatCallback,
|
|
22301
22486
|
};
|
|
22302
|
-
return config;
|
|
22303
|
-
}
|
|
22304
|
-
function createBarChartRuntime(chart, getters) {
|
|
22305
|
-
const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
|
|
22306
|
-
let labels = labelValues.formattedValues;
|
|
22307
|
-
let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
|
|
22308
|
-
if (chart.dataSetsHaveTitle &&
|
|
22309
|
-
dataSetsValues[0] &&
|
|
22310
|
-
labels.length > dataSetsValues[0].data.length) {
|
|
22311
|
-
labels.shift();
|
|
22312
|
-
}
|
|
22313
|
-
({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
|
|
22314
|
-
if (chart.aggregated) {
|
|
22315
|
-
({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
|
|
22316
|
-
}
|
|
22317
|
-
const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
|
|
22318
|
-
const locale = getters.getLocale();
|
|
22319
|
-
const config = getBarConfiguration(chart, labels, { format: dataSetFormat, locale });
|
|
22320
22487
|
const colors = new ColorGenerator();
|
|
22488
|
+
const trendDatasets = [];
|
|
22321
22489
|
const definition = chart.getDefinition();
|
|
22322
|
-
for (const
|
|
22490
|
+
for (const index in dataSetsValues) {
|
|
22491
|
+
const { label, data } = dataSetsValues[index];
|
|
22323
22492
|
const color = colors.next();
|
|
22324
22493
|
const dataset = {
|
|
22325
22494
|
label,
|
|
@@ -22328,8 +22497,6 @@ function createBarChartRuntime(chart, getters) {
|
|
|
22328
22497
|
backgroundColor: color,
|
|
22329
22498
|
};
|
|
22330
22499
|
config.data.datasets.push(dataset);
|
|
22331
|
-
}
|
|
22332
|
-
for (const [index, dataset] of config.data.datasets.entries()) {
|
|
22333
22500
|
if (definition.dataSets?.[index]?.backgroundColor) {
|
|
22334
22501
|
const color = definition.dataSets[index].backgroundColor;
|
|
22335
22502
|
dataset.backgroundColor = color;
|
|
@@ -22342,6 +22509,30 @@ function createBarChartRuntime(chart, getters) {
|
|
|
22342
22509
|
if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
|
|
22343
22510
|
dataset["yAxisID"] = definition.dataSets[index].yAxisId;
|
|
22344
22511
|
}
|
|
22512
|
+
const trend = definition.dataSets?.[index].trend;
|
|
22513
|
+
if (!trend?.display || chart.horizontal) {
|
|
22514
|
+
continue;
|
|
22515
|
+
}
|
|
22516
|
+
const trendDataset = getTrendDatasetForBarChart(trend, dataset);
|
|
22517
|
+
if (trendDataset) {
|
|
22518
|
+
trendDatasets.push(trendDataset);
|
|
22519
|
+
}
|
|
22520
|
+
}
|
|
22521
|
+
if (trendDatasets.length) {
|
|
22522
|
+
/* We add a second x axis here to draw the trend lines, with the labels length being
|
|
22523
|
+
* set so that the second axis points match the classical x axis
|
|
22524
|
+
*/
|
|
22525
|
+
const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset.data.length));
|
|
22526
|
+
config.options.scales[TREND_LINE_XAXIS_ID] = {
|
|
22527
|
+
...xAxis,
|
|
22528
|
+
labels: Array(maxLength).fill(""),
|
|
22529
|
+
offset: false,
|
|
22530
|
+
display: false,
|
|
22531
|
+
};
|
|
22532
|
+
/* These datasets must be inserted after the original
|
|
22533
|
+
* datasets to ensure the way we distinguish the originals and trendLine datasets after
|
|
22534
|
+
*/
|
|
22535
|
+
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
22345
22536
|
}
|
|
22346
22537
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
22347
22538
|
}
|
|
@@ -22540,90 +22731,58 @@ function isLuxonTimeAdapterInstalled() {
|
|
|
22540
22731
|
}
|
|
22541
22732
|
return isInstalled;
|
|
22542
22733
|
}
|
|
22543
|
-
function
|
|
22544
|
-
const
|
|
22545
|
-
const
|
|
22546
|
-
const
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
label.fillStyle = data.datasets[index].borderColor;
|
|
22734
|
+
function getTrendDatasetForLineChart(config, dataset, axisType, locale) {
|
|
22735
|
+
const filteredValues = [];
|
|
22736
|
+
const filteredLabels = [];
|
|
22737
|
+
const labels = [];
|
|
22738
|
+
const datasetLength = dataset.data.length;
|
|
22739
|
+
switch (axisType) {
|
|
22740
|
+
case "category":
|
|
22741
|
+
for (let i = 0; i < datasetLength; i++) {
|
|
22742
|
+
if (dataset.data[i] !== null) {
|
|
22743
|
+
filteredValues.push(dataset.data[i]);
|
|
22744
|
+
filteredLabels.push(i + 1);
|
|
22555
22745
|
}
|
|
22556
|
-
|
|
22557
|
-
}
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
return value;
|
|
22583
|
-
const { locale, format } = options;
|
|
22584
|
-
return formatValue(value, {
|
|
22585
|
-
locale,
|
|
22586
|
-
format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
|
|
22587
|
-
});
|
|
22588
|
-
};
|
|
22589
|
-
const yAxis = {
|
|
22590
|
-
beginAtZero: true, // the origin of the y axis is always zero
|
|
22591
|
-
ticks: {
|
|
22592
|
-
color: fontColor,
|
|
22593
|
-
callback: formatCallback,
|
|
22594
|
-
},
|
|
22595
|
-
};
|
|
22596
|
-
const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
|
|
22597
|
-
if (useLeftAxis) {
|
|
22598
|
-
config.options.scales.y = {
|
|
22599
|
-
...yAxis,
|
|
22600
|
-
position: "left",
|
|
22601
|
-
title: getChartAxisTitleRuntime(chart.axesDesign?.y),
|
|
22602
|
-
};
|
|
22746
|
+
labels.push(i + 1);
|
|
22747
|
+
}
|
|
22748
|
+
break;
|
|
22749
|
+
case "linear":
|
|
22750
|
+
for (const point of dataset.data) {
|
|
22751
|
+
const label = Number(point.x);
|
|
22752
|
+
if (isNaN(label)) {
|
|
22753
|
+
continue;
|
|
22754
|
+
}
|
|
22755
|
+
if (point.y !== null) {
|
|
22756
|
+
filteredValues.push(point.y);
|
|
22757
|
+
filteredLabels.push(label);
|
|
22758
|
+
}
|
|
22759
|
+
labels.push(label);
|
|
22760
|
+
}
|
|
22761
|
+
break;
|
|
22762
|
+
case "time":
|
|
22763
|
+
for (const point of dataset.data) {
|
|
22764
|
+
const date = toJsDate({ value: point.x }, locale).getTime();
|
|
22765
|
+
if (point.y !== null) {
|
|
22766
|
+
filteredValues.push(point.y);
|
|
22767
|
+
filteredLabels.push(date);
|
|
22768
|
+
}
|
|
22769
|
+
labels.push(date);
|
|
22770
|
+
}
|
|
22771
|
+
break;
|
|
22603
22772
|
}
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
|
|
22609
|
-
};
|
|
22773
|
+
const xmin = Math.min(...labels);
|
|
22774
|
+
const xmax = Math.max(...labels);
|
|
22775
|
+
if (xmax === xmin) {
|
|
22776
|
+
return;
|
|
22610
22777
|
}
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
// @ts-ignore chart.js type is broken
|
|
22618
|
-
config.options.scales.y1.stacked = true;
|
|
22619
|
-
}
|
|
22778
|
+
const numberOfStep = 5 * labels.length;
|
|
22779
|
+
const step = (xmax - xmin) / numberOfStep;
|
|
22780
|
+
const newLabels = range(xmin, xmax + step / 2, step);
|
|
22781
|
+
const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
|
|
22782
|
+
if (!newValues.length) {
|
|
22783
|
+
return;
|
|
22620
22784
|
}
|
|
22621
|
-
config
|
|
22622
|
-
showValues: chart.showValues,
|
|
22623
|
-
background: chart.background,
|
|
22624
|
-
callback: formatCallback,
|
|
22625
|
-
};
|
|
22626
|
-
return config;
|
|
22785
|
+
return getFullTrendingLineDataSet(dataset, config, newValues);
|
|
22627
22786
|
}
|
|
22628
22787
|
function createLineOrScatterChartRuntime(chart, getters) {
|
|
22629
22788
|
const axisType = getChartAxisType(chart, getters);
|
|
@@ -22646,7 +22805,97 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
22646
22805
|
const truncateLabels = axisType === "category";
|
|
22647
22806
|
const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
|
|
22648
22807
|
const options = { format: dataSetFormat, locale, truncateLabels };
|
|
22649
|
-
const
|
|
22808
|
+
const fontColor = chartFontColor(chart.background);
|
|
22809
|
+
const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
|
|
22810
|
+
const legend = {
|
|
22811
|
+
labels: {
|
|
22812
|
+
color: fontColor,
|
|
22813
|
+
generateLabels(chart) {
|
|
22814
|
+
// color the legend labels with the dataset color, without any transparency
|
|
22815
|
+
const { data } = chart;
|
|
22816
|
+
const labels = window.Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
|
22817
|
+
for (const [index, label] of labels.entries()) {
|
|
22818
|
+
label.fillStyle = data.datasets[index].borderColor;
|
|
22819
|
+
}
|
|
22820
|
+
return labels;
|
|
22821
|
+
},
|
|
22822
|
+
},
|
|
22823
|
+
};
|
|
22824
|
+
if (chart.legendPosition === "none") {
|
|
22825
|
+
legend.display = false;
|
|
22826
|
+
}
|
|
22827
|
+
else {
|
|
22828
|
+
legend.position = chart.legendPosition;
|
|
22829
|
+
}
|
|
22830
|
+
Object.assign(config.options.plugins.legend || {}, legend);
|
|
22831
|
+
config.options.layout = {
|
|
22832
|
+
padding: computeChartPadding({
|
|
22833
|
+
displayTitle: !!chart.title.text,
|
|
22834
|
+
displayLegend: chart.legendPosition === "top",
|
|
22835
|
+
}),
|
|
22836
|
+
};
|
|
22837
|
+
const xAxis = {
|
|
22838
|
+
ticks: {
|
|
22839
|
+
padding: 5,
|
|
22840
|
+
color: fontColor,
|
|
22841
|
+
},
|
|
22842
|
+
title: getChartAxisTitleRuntime(chart.axesDesign?.x),
|
|
22843
|
+
};
|
|
22844
|
+
config.options.scales = {
|
|
22845
|
+
x: xAxis,
|
|
22846
|
+
};
|
|
22847
|
+
const formatCallback = (value) => {
|
|
22848
|
+
value = Number(value);
|
|
22849
|
+
if (isNaN(value))
|
|
22850
|
+
return value;
|
|
22851
|
+
const { locale, format } = options;
|
|
22852
|
+
return formatValue(value, {
|
|
22853
|
+
locale,
|
|
22854
|
+
format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
|
|
22855
|
+
});
|
|
22856
|
+
};
|
|
22857
|
+
const yAxis = {
|
|
22858
|
+
beginAtZero: true, // the origin of the y axis is always zero
|
|
22859
|
+
ticks: {
|
|
22860
|
+
color: fontColor,
|
|
22861
|
+
callback: formatCallback,
|
|
22862
|
+
},
|
|
22863
|
+
};
|
|
22864
|
+
const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
|
|
22865
|
+
if (useLeftAxis) {
|
|
22866
|
+
config.options.scales.y = {
|
|
22867
|
+
...yAxis,
|
|
22868
|
+
position: "left",
|
|
22869
|
+
title: getChartAxisTitleRuntime(chart.axesDesign?.y),
|
|
22870
|
+
};
|
|
22871
|
+
}
|
|
22872
|
+
if (useRightAxis) {
|
|
22873
|
+
config.options.scales.y1 = {
|
|
22874
|
+
...yAxis,
|
|
22875
|
+
position: "right",
|
|
22876
|
+
title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
|
|
22877
|
+
};
|
|
22878
|
+
}
|
|
22879
|
+
if ("stacked" in chart && chart.stacked) {
|
|
22880
|
+
if (useLeftAxis) {
|
|
22881
|
+
// @ts-ignore chart.js type is broken
|
|
22882
|
+
config.options.scales.y.stacked = true;
|
|
22883
|
+
}
|
|
22884
|
+
if (useRightAxis) {
|
|
22885
|
+
// @ts-ignore chart.js type is broken
|
|
22886
|
+
config.options.scales.y1.stacked = true;
|
|
22887
|
+
}
|
|
22888
|
+
}
|
|
22889
|
+
config.options.plugins.chartShowValuesPlugin = {
|
|
22890
|
+
showValues: chart.showValues,
|
|
22891
|
+
background: chart.background,
|
|
22892
|
+
callback: formatCallback,
|
|
22893
|
+
};
|
|
22894
|
+
if (chart.dataSetsHaveTitle &&
|
|
22895
|
+
dataSetsValues[0] &&
|
|
22896
|
+
labels.length > dataSetsValues[0].data.length) {
|
|
22897
|
+
labels.shift();
|
|
22898
|
+
}
|
|
22650
22899
|
const labelFormat = getChartLabelFormat(getters, chart.labelRange);
|
|
22651
22900
|
if (axisType === "time") {
|
|
22652
22901
|
const axis = {
|
|
@@ -22659,11 +22908,19 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
22659
22908
|
else if (axisType === "linear") {
|
|
22660
22909
|
config.options.scales.x.type = "linear";
|
|
22661
22910
|
config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
|
|
22662
|
-
config.options.plugins.tooltip.callbacks.title = (
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22911
|
+
config.options.plugins.tooltip.callbacks.title = () => "";
|
|
22912
|
+
config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
|
|
22913
|
+
const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
|
|
22914
|
+
let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
|
|
22915
|
+
if (isNumber(label, locale)) {
|
|
22916
|
+
label = toNumber(label, locale);
|
|
22917
|
+
}
|
|
22918
|
+
const formattedX = formatValue(label, { locale, format: labelFormat });
|
|
22919
|
+
const formattedY = formatValue(dataSetPoint, { locale, format: dataSetFormat });
|
|
22920
|
+
const dataSetTitle = tooltipItem.dataset.label;
|
|
22921
|
+
return formattedX
|
|
22922
|
+
? `${dataSetTitle}: (${formattedX}, ${formattedY})`
|
|
22923
|
+
: `${dataSetTitle}: ${formattedY}`;
|
|
22667
22924
|
};
|
|
22668
22925
|
}
|
|
22669
22926
|
const areaChart = "fillArea" in chart ? chart.fillArea : false;
|
|
@@ -22703,6 +22960,8 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
22703
22960
|
};
|
|
22704
22961
|
config.data.datasets.push(dataset);
|
|
22705
22962
|
}
|
|
22963
|
+
let maxLength = 0;
|
|
22964
|
+
const trendDatasets = [];
|
|
22706
22965
|
for (const [index, dataset] of config.data.datasets.entries()) {
|
|
22707
22966
|
if (definition.dataSets?.[index]?.backgroundColor) {
|
|
22708
22967
|
const color = definition.dataSets[index].backgroundColor;
|
|
@@ -22718,6 +22977,32 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
22718
22977
|
if (definition.dataSets?.[index]?.yAxisId) {
|
|
22719
22978
|
dataset["yAxisID"] = definition.dataSets[index].yAxisId;
|
|
22720
22979
|
}
|
|
22980
|
+
const trend = definition.dataSets?.[index].trend;
|
|
22981
|
+
if (!trend?.display) {
|
|
22982
|
+
continue;
|
|
22983
|
+
}
|
|
22984
|
+
const trendDataset = getTrendDatasetForLineChart(trend, dataset, axisType, locale);
|
|
22985
|
+
if (trendDataset) {
|
|
22986
|
+
maxLength = Math.max(maxLength, trendDataset.data.length);
|
|
22987
|
+
trendDatasets.push(trendDataset);
|
|
22988
|
+
dataSetsValues.push(trendDataset);
|
|
22989
|
+
}
|
|
22990
|
+
}
|
|
22991
|
+
if (trendDatasets.length) {
|
|
22992
|
+
/* We add a second x axis here to draw the trend lines, with the labels length being
|
|
22993
|
+
* set so that the second axis points match the classical x axis
|
|
22994
|
+
*/
|
|
22995
|
+
config.options.scales[TREND_LINE_XAXIS_ID] = {
|
|
22996
|
+
...xAxis,
|
|
22997
|
+
type: "category",
|
|
22998
|
+
labels: range(0, maxLength).map((x) => x.toString()),
|
|
22999
|
+
offset: false,
|
|
23000
|
+
display: false,
|
|
23001
|
+
};
|
|
23002
|
+
/* These datasets must be inserted after the original datasets to ensure the way we
|
|
23003
|
+
* distinguish the originals and trendLine datasets after
|
|
23004
|
+
*/
|
|
23005
|
+
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
22721
23006
|
}
|
|
22722
23007
|
return {
|
|
22723
23008
|
chartJsConfig: config,
|
|
@@ -22877,7 +23162,7 @@ function createComboChartRuntime(chart, getters) {
|
|
|
22877
23162
|
labels: { color: fontColor },
|
|
22878
23163
|
reverse: true,
|
|
22879
23164
|
};
|
|
22880
|
-
if (
|
|
23165
|
+
if (chart.legendPosition === "none") {
|
|
22881
23166
|
legend.display = false;
|
|
22882
23167
|
}
|
|
22883
23168
|
else {
|
|
@@ -22885,7 +23170,10 @@ function createComboChartRuntime(chart, getters) {
|
|
|
22885
23170
|
}
|
|
22886
23171
|
config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
|
|
22887
23172
|
config.options.layout = {
|
|
22888
|
-
padding: {
|
|
23173
|
+
padding: computeChartPadding({
|
|
23174
|
+
displayTitle: !!chart.title.text,
|
|
23175
|
+
displayLegend: chart.legendPosition === "top",
|
|
23176
|
+
}),
|
|
22889
23177
|
};
|
|
22890
23178
|
config.options.scales = {
|
|
22891
23179
|
x: {
|
|
@@ -22947,6 +23235,8 @@ function createComboChartRuntime(chart, getters) {
|
|
|
22947
23235
|
callback: formatCallback(mainDataSetFormat),
|
|
22948
23236
|
};
|
|
22949
23237
|
const colors = new ColorGenerator();
|
|
23238
|
+
let maxLength = 0;
|
|
23239
|
+
const trendDatasets = [];
|
|
22950
23240
|
for (let [index, { label, data }] of dataSetsValues.entries()) {
|
|
22951
23241
|
const design = definition.dataSets[index];
|
|
22952
23242
|
const color = colors.next();
|
|
@@ -22960,6 +23250,33 @@ function createComboChartRuntime(chart, getters) {
|
|
|
22960
23250
|
order: -index,
|
|
22961
23251
|
};
|
|
22962
23252
|
config.data.datasets.push(dataset);
|
|
23253
|
+
const trend = definition.dataSets?.[index].trend;
|
|
23254
|
+
if (!trend?.display) {
|
|
23255
|
+
continue;
|
|
23256
|
+
}
|
|
23257
|
+
maxLength = Math.max(maxLength, data.length);
|
|
23258
|
+
const trendDataset = getTrendDatasetForBarChart(trend, dataset);
|
|
23259
|
+
if (trendDataset) {
|
|
23260
|
+
trendDatasets.push(trendDataset);
|
|
23261
|
+
}
|
|
23262
|
+
}
|
|
23263
|
+
if (trendDatasets.length) {
|
|
23264
|
+
/* We add a second x axis here to draw the trend lines, with the labels length being
|
|
23265
|
+
* set so that the second axis points match the classical x axis
|
|
23266
|
+
*/
|
|
23267
|
+
config.options.scales[TREND_LINE_XAXIS_ID] = {
|
|
23268
|
+
ticks: {
|
|
23269
|
+
padding: 5,
|
|
23270
|
+
color: fontColor,
|
|
23271
|
+
},
|
|
23272
|
+
labels: Array(10 * maxLength + 1).fill(""),
|
|
23273
|
+
offset: false,
|
|
23274
|
+
display: false,
|
|
23275
|
+
};
|
|
23276
|
+
/* These datasets must be inserted after the original datasets to ensure the way we
|
|
23277
|
+
* distinguish the originals and trendLine datasets after
|
|
23278
|
+
*/
|
|
23279
|
+
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
22963
23280
|
}
|
|
22964
23281
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
22965
23282
|
}
|
|
@@ -23816,28 +24133,12 @@ class ScatterChart extends AbstractChart {
|
|
|
23816
24133
|
}
|
|
23817
24134
|
}
|
|
23818
24135
|
function createScatterChartRuntime(chart, getters) {
|
|
23819
|
-
const { chartJsConfig, background
|
|
24136
|
+
const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
|
|
23820
24137
|
// use chartJS line chart and disable the lines instead of chartJS scatter chart. This is because the scatter chart
|
|
23821
24138
|
// have less options than the line chart (it only works with linear labels)
|
|
23822
24139
|
chartJsConfig.type = "line";
|
|
23823
|
-
const configOptions = chartJsConfig.options;
|
|
23824
|
-
const locale = getters.getLocale();
|
|
23825
|
-
configOptions.plugins.tooltip.callbacks.title = () => "";
|
|
23826
|
-
configOptions.plugins.tooltip.callbacks.label = (tooltipItem) => {
|
|
23827
|
-
const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
|
|
23828
|
-
let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
|
|
23829
|
-
if (isNumber(label, locale)) {
|
|
23830
|
-
label = toNumber(label, locale);
|
|
23831
|
-
}
|
|
23832
|
-
const formattedX = formatValue(label, { locale, format: labelFormat });
|
|
23833
|
-
const formattedY = formatValue(dataSetPoint, { locale, format: dataSetFormat });
|
|
23834
|
-
const dataSetTitle = tooltipItem.dataset.label;
|
|
23835
|
-
return formattedX
|
|
23836
|
-
? `${dataSetTitle}: (${formattedX}, ${formattedY})`
|
|
23837
|
-
: `${dataSetTitle}: ${formattedY}`;
|
|
23838
|
-
};
|
|
23839
24140
|
for (const dataSet of chartJsConfig.data.datasets) {
|
|
23840
|
-
dataSet.showLine = false;
|
|
24141
|
+
dataSet.showLine = "showLine" in dataSet ? dataSet.showLine : false;
|
|
23841
24142
|
}
|
|
23842
24143
|
return { chartJsConfig, background };
|
|
23843
24144
|
}
|
|
@@ -24875,78 +25176,6 @@ function zoneToRect(zone) {
|
|
|
24875
25176
|
};
|
|
24876
25177
|
}
|
|
24877
25178
|
|
|
24878
|
-
/**
|
|
24879
|
-
* Return the o-spreadsheet element position relative
|
|
24880
|
-
* to the browser viewport.
|
|
24881
|
-
*/
|
|
24882
|
-
function useSpreadsheetRect() {
|
|
24883
|
-
const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
24884
|
-
let spreadsheetElement = null;
|
|
24885
|
-
function updatePosition() {
|
|
24886
|
-
if (!spreadsheetElement) {
|
|
24887
|
-
spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
24888
|
-
}
|
|
24889
|
-
if (spreadsheetElement) {
|
|
24890
|
-
const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
|
|
24891
|
-
position.x = left;
|
|
24892
|
-
position.y = top;
|
|
24893
|
-
position.width = width;
|
|
24894
|
-
position.height = height;
|
|
24895
|
-
}
|
|
24896
|
-
}
|
|
24897
|
-
owl.onMounted(updatePosition);
|
|
24898
|
-
owl.onPatched(updatePosition);
|
|
24899
|
-
return position;
|
|
24900
|
-
}
|
|
24901
|
-
/**
|
|
24902
|
-
* Return the component (or ref's component) BoundingRect, relative
|
|
24903
|
-
* to the upper left corner of the screen (<body> element).
|
|
24904
|
-
*
|
|
24905
|
-
* Note: when used with a <Portal/> component, it will
|
|
24906
|
-
* return the portal position, not the teleported position.
|
|
24907
|
-
*/
|
|
24908
|
-
function useAbsoluteBoundingRect(ref) {
|
|
24909
|
-
const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
24910
|
-
function updateElRect() {
|
|
24911
|
-
const el = ref.el;
|
|
24912
|
-
if (el === null) {
|
|
24913
|
-
return;
|
|
24914
|
-
}
|
|
24915
|
-
const { top, left, width, height } = el.getBoundingClientRect();
|
|
24916
|
-
rect.x = left;
|
|
24917
|
-
rect.y = top;
|
|
24918
|
-
rect.width = width;
|
|
24919
|
-
rect.height = height;
|
|
24920
|
-
}
|
|
24921
|
-
owl.onMounted(updateElRect);
|
|
24922
|
-
owl.onPatched(updateElRect);
|
|
24923
|
-
return rect;
|
|
24924
|
-
}
|
|
24925
|
-
/**
|
|
24926
|
-
* Get the rectangle inside which a popover should stay when being displayed.
|
|
24927
|
-
* It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
|
|
24928
|
-
* element by default.
|
|
24929
|
-
*
|
|
24930
|
-
* Coordinates are expressed expressed as absolute DOM position.
|
|
24931
|
-
*/
|
|
24932
|
-
function usePopoverContainer() {
|
|
24933
|
-
const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
24934
|
-
const component = owl.useComponent();
|
|
24935
|
-
const spreadsheetRect = useSpreadsheetRect();
|
|
24936
|
-
function updateRect() {
|
|
24937
|
-
const env = component.env;
|
|
24938
|
-
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
24939
|
-
container.x = newRect.x;
|
|
24940
|
-
container.y = newRect.y;
|
|
24941
|
-
container.width = newRect.width;
|
|
24942
|
-
container.height = newRect.height;
|
|
24943
|
-
}
|
|
24944
|
-
updateRect();
|
|
24945
|
-
owl.onMounted(updateRect);
|
|
24946
|
-
owl.onPatched(updateRect);
|
|
24947
|
-
return container;
|
|
24948
|
-
}
|
|
24949
|
-
|
|
24950
25179
|
css /* scss */ `
|
|
24951
25180
|
.o-popover {
|
|
24952
25181
|
position: absolute;
|
|
@@ -27714,7 +27943,7 @@ const FIX_FORMULAS = {
|
|
|
27714
27943
|
if (!pivot.isValid()) {
|
|
27715
27944
|
return;
|
|
27716
27945
|
}
|
|
27717
|
-
env.model.dispatch("
|
|
27946
|
+
env.model.dispatch("SPLIT_PIVOT_FORMULA", {
|
|
27718
27947
|
sheetId,
|
|
27719
27948
|
col,
|
|
27720
27949
|
row,
|
|
@@ -31190,11 +31419,7 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31190
31419
|
});
|
|
31191
31420
|
}
|
|
31192
31421
|
getDataSeries() {
|
|
31193
|
-
|
|
31194
|
-
if (!runtime || !("chartJsConfig" in runtime)) {
|
|
31195
|
-
return [];
|
|
31196
|
-
}
|
|
31197
|
-
return runtime.chartJsConfig.data.datasets.map((d) => d.label);
|
|
31422
|
+
return this.props.definition.dataSets.map((d, i) => d.label ?? `${ChartTerms.Series} ${i + 1}`);
|
|
31198
31423
|
}
|
|
31199
31424
|
updateSerieEditor(ev) {
|
|
31200
31425
|
const chartId = this.props.figureId;
|
|
@@ -31206,9 +31431,10 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31206
31431
|
this.state.index = selectedIndex;
|
|
31207
31432
|
}
|
|
31208
31433
|
updateDataSeriesColor(color) {
|
|
31209
|
-
const dataSets = this.props.definition.dataSets;
|
|
31210
|
-
if (!dataSets?.[this.state.index])
|
|
31434
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
31435
|
+
if (!dataSets?.[this.state.index]) {
|
|
31211
31436
|
return;
|
|
31437
|
+
}
|
|
31212
31438
|
dataSets[this.state.index] = {
|
|
31213
31439
|
...dataSets[this.state.index],
|
|
31214
31440
|
backgroundColor: color,
|
|
@@ -31217,16 +31443,18 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31217
31443
|
}
|
|
31218
31444
|
getDataSerieColor() {
|
|
31219
31445
|
const dataSets = this.props.definition.dataSets;
|
|
31220
|
-
if (!dataSets?.[this.state.index])
|
|
31446
|
+
if (!dataSets?.[this.state.index]) {
|
|
31221
31447
|
return "";
|
|
31448
|
+
}
|
|
31222
31449
|
const color = dataSets[this.state.index].backgroundColor;
|
|
31223
31450
|
return color ? toHex(color) : getNthColor(this.state.index);
|
|
31224
31451
|
}
|
|
31225
31452
|
updateDataSeriesAxis(ev) {
|
|
31226
31453
|
const axis = ev.target.value;
|
|
31227
|
-
const dataSets = this.props.definition.dataSets;
|
|
31228
|
-
if (!dataSets?.[this.state.index])
|
|
31454
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
31455
|
+
if (!dataSets?.[this.state.index]) {
|
|
31229
31456
|
return;
|
|
31457
|
+
}
|
|
31230
31458
|
dataSets[this.state.index] = {
|
|
31231
31459
|
...dataSets[this.state.index],
|
|
31232
31460
|
yAxisId: axis === "left" ? "y" : "y1",
|
|
@@ -31235,8 +31463,9 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31235
31463
|
}
|
|
31236
31464
|
getDataSerieAxis() {
|
|
31237
31465
|
const dataSets = this.props.definition.dataSets;
|
|
31238
|
-
if (!dataSets?.[this.state.index])
|
|
31466
|
+
if (!dataSets?.[this.state.index]) {
|
|
31239
31467
|
return "left";
|
|
31468
|
+
}
|
|
31240
31469
|
return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
|
|
31241
31470
|
}
|
|
31242
31471
|
get canHaveTwoVerticalAxis() {
|
|
@@ -31244,9 +31473,10 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31244
31473
|
}
|
|
31245
31474
|
updateDataSeriesLabel(ev) {
|
|
31246
31475
|
const label = ev.target.value;
|
|
31247
|
-
const dataSets = this.props.definition.dataSets;
|
|
31248
|
-
if (!dataSets?.[this.state.index])
|
|
31476
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
31477
|
+
if (!dataSets?.[this.state.index]) {
|
|
31249
31478
|
return;
|
|
31479
|
+
}
|
|
31250
31480
|
dataSets[this.state.index] = {
|
|
31251
31481
|
...dataSets[this.state.index],
|
|
31252
31482
|
label,
|
|
@@ -31263,6 +31493,81 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
31263
31493
|
updateShowValues(showValues) {
|
|
31264
31494
|
this.props.updateChart(this.props.figureId, { showValues });
|
|
31265
31495
|
}
|
|
31496
|
+
toggleDataTrend(display) {
|
|
31497
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
31498
|
+
if (!dataSets?.[this.state.index]) {
|
|
31499
|
+
return;
|
|
31500
|
+
}
|
|
31501
|
+
dataSets[this.state.index] = {
|
|
31502
|
+
...dataSets[this.state.index],
|
|
31503
|
+
trend: {
|
|
31504
|
+
type: "polynomial",
|
|
31505
|
+
order: 1,
|
|
31506
|
+
...dataSets[this.state.index].trend,
|
|
31507
|
+
display,
|
|
31508
|
+
},
|
|
31509
|
+
};
|
|
31510
|
+
this.props.updateChart(this.props.figureId, { dataSets });
|
|
31511
|
+
}
|
|
31512
|
+
getTrendLineConfiguration() {
|
|
31513
|
+
const dataSets = this.props.definition.dataSets;
|
|
31514
|
+
return dataSets?.[this.state.index]?.trend;
|
|
31515
|
+
}
|
|
31516
|
+
getTrendType(config) {
|
|
31517
|
+
if (!config) {
|
|
31518
|
+
return "";
|
|
31519
|
+
}
|
|
31520
|
+
return config.type === "polynomial" && config.order === 1 ? "linear" : config.type;
|
|
31521
|
+
}
|
|
31522
|
+
onChangeTrendType(ev) {
|
|
31523
|
+
const type = ev.target.value;
|
|
31524
|
+
let config;
|
|
31525
|
+
switch (type) {
|
|
31526
|
+
case "linear":
|
|
31527
|
+
case "polynomial":
|
|
31528
|
+
config = {
|
|
31529
|
+
type: "polynomial",
|
|
31530
|
+
order: type === "linear" ? 1 : 2,
|
|
31531
|
+
};
|
|
31532
|
+
break;
|
|
31533
|
+
case "exponential":
|
|
31534
|
+
case "logarithmic":
|
|
31535
|
+
config = { type };
|
|
31536
|
+
break;
|
|
31537
|
+
default:
|
|
31538
|
+
return;
|
|
31539
|
+
}
|
|
31540
|
+
this.updateTrendLineValue(config);
|
|
31541
|
+
}
|
|
31542
|
+
onChangePolynomialDegree(ev) {
|
|
31543
|
+
const element = ev.target;
|
|
31544
|
+
const order = parseInt(element.value || "1");
|
|
31545
|
+
if (order < 2) {
|
|
31546
|
+
element.value = `${this.getTrendLineConfiguration()?.order ?? 2}`;
|
|
31547
|
+
return;
|
|
31548
|
+
}
|
|
31549
|
+
this.updateTrendLineValue({ order });
|
|
31550
|
+
}
|
|
31551
|
+
getTrendLineColor() {
|
|
31552
|
+
return this.getTrendLineConfiguration()?.color ?? setColorAlpha(this.getDataSerieColor(), 0.5);
|
|
31553
|
+
}
|
|
31554
|
+
updateTrendLineColor(color) {
|
|
31555
|
+
this.updateTrendLineValue({ color });
|
|
31556
|
+
}
|
|
31557
|
+
updateTrendLineValue(config) {
|
|
31558
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
31559
|
+
if (!dataSets?.[this.state.index]) {
|
|
31560
|
+
return;
|
|
31561
|
+
}
|
|
31562
|
+
dataSets[this.state.index] = {
|
|
31563
|
+
...dataSets[this.state.index],
|
|
31564
|
+
trend: {
|
|
31565
|
+
...dataSets[this.state.index].trend,
|
|
31566
|
+
...config,
|
|
31567
|
+
},
|
|
31568
|
+
};
|
|
31569
|
+
this.props.updateChart(this.props.figureId, { dataSets });
|
|
31570
|
+
}
|
|
31266
31571
|
}
|
|
31267
31572
|
|
|
31268
31573
|
class GaugeChartConfigPanel extends owl.Component {
|
|
@@ -33584,10 +33889,11 @@ class ConditionalFormattingEditor extends owl.Component {
|
|
|
33584
33889
|
* Cell Is Rule
|
|
33585
33890
|
****************************************************************************/
|
|
33586
33891
|
get isValue1Invalid() {
|
|
33587
|
-
return
|
|
33892
|
+
return (this.state.errors.includes("FirstArgMissing" /* CommandResult.FirstArgMissing */) ||
|
|
33893
|
+
this.state.errors.includes("ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */));
|
|
33588
33894
|
}
|
|
33589
33895
|
get isValue2Invalid() {
|
|
33590
|
-
return
|
|
33896
|
+
return this.state.errors.includes("SecondArgMissing" /* CommandResult.SecondArgMissing */);
|
|
33591
33897
|
}
|
|
33592
33898
|
toggleStyle(tool) {
|
|
33593
33899
|
const style = this.state.rules.cellIs.style;
|
|
@@ -35826,6 +36132,14 @@ css /* scss */ `
|
|
|
35826
36132
|
.pivot-dim-operator-label {
|
|
35827
36133
|
min-width: 120px;
|
|
35828
36134
|
}
|
|
36135
|
+
|
|
36136
|
+
&.pivot-dimension-invalid {
|
|
36137
|
+
background-color: #ffdddd;
|
|
36138
|
+
border-color: red !important;
|
|
36139
|
+
select {
|
|
36140
|
+
background-color: #ffdddd;
|
|
36141
|
+
}
|
|
36142
|
+
}
|
|
35829
36143
|
}
|
|
35830
36144
|
`;
|
|
35831
36145
|
class PivotDimension extends owl.Component {
|
|
@@ -47572,12 +47886,13 @@ class BordersPlugin extends CorePlugin {
|
|
|
47572
47886
|
this.clearBorders(cmd.sheetId, cmd.target);
|
|
47573
47887
|
break;
|
|
47574
47888
|
case "REMOVE_COLUMNS_ROWS":
|
|
47575
|
-
|
|
47889
|
+
const elements = [...cmd.elements].sort((a, b) => b - a);
|
|
47890
|
+
for (const group of groupConsecutive(elements)) {
|
|
47576
47891
|
if (cmd.dimension === "COL") {
|
|
47577
|
-
this.shiftBordersHorizontally(cmd.sheetId,
|
|
47892
|
+
this.shiftBordersHorizontally(cmd.sheetId, group[group.length - 1] + 1, -group.length);
|
|
47578
47893
|
}
|
|
47579
47894
|
else {
|
|
47580
|
-
this.shiftBordersVertically(cmd.sheetId,
|
|
47895
|
+
this.shiftBordersVertically(cmd.sheetId, group[group.length - 1] + 1, -group.length);
|
|
47581
47896
|
}
|
|
47582
47897
|
}
|
|
47583
47898
|
break;
|
|
@@ -49014,7 +49329,7 @@ class ConditionalFormatPlugin extends CorePlugin {
|
|
|
49014
49329
|
"LessThan",
|
|
49015
49330
|
"LessThanOrEqual",
|
|
49016
49331
|
"NotContains",
|
|
49017
|
-
]), this.checkOperatorArgsNumber(0, ["IsEmpty", "IsNotEmpty"]));
|
|
49332
|
+
]), this.checkOperatorArgsNumber(0, ["IsEmpty", "IsNotEmpty"]), this.checkCFValues);
|
|
49018
49333
|
case "ColorScaleRule": {
|
|
49019
49334
|
return this.checkValidations(rule, this.chainValidations(this.checkThresholds(this.checkFormulaCompilation)), this.chainValidations(this.checkThresholds(this.checkNaN), this.batchValidations(this.checkMinBiggerThanMax, this.checkMinBiggerThanMid, this.checkMidBiggerThanMax
|
|
49020
49335
|
// Those three validations can be factorized further
|
|
@@ -49131,6 +49446,17 @@ class ConditionalFormatPlugin extends CorePlugin {
|
|
|
49131
49446
|
}
|
|
49132
49447
|
return "Success" /* CommandResult.Success */;
|
|
49133
49448
|
}
|
|
49449
|
+
checkCFValues(rule) {
|
|
49450
|
+
for (const value of rule.values) {
|
|
49451
|
+
if (!value.startsWith("="))
|
|
49452
|
+
continue;
|
|
49453
|
+
const compiledFormula = compile(value || "");
|
|
49454
|
+
if (compiledFormula.isBadExpression) {
|
|
49455
|
+
return "ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */;
|
|
49456
|
+
}
|
|
49457
|
+
}
|
|
49458
|
+
return "Success" /* CommandResult.Success */;
|
|
49459
|
+
}
|
|
49134
49460
|
removeConditionalFormatting(id, sheet) {
|
|
49135
49461
|
const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id);
|
|
49136
49462
|
if (cfIndex !== -1) {
|
|
@@ -51615,12 +51941,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
51615
51941
|
});
|
|
51616
51942
|
}
|
|
51617
51943
|
if (colIndex > deletedColumn) {
|
|
51618
|
-
this.
|
|
51619
|
-
sheetId: sheet.id,
|
|
51620
|
-
cellId: cellId,
|
|
51621
|
-
col: colIndex - 1,
|
|
51622
|
-
row: rowIndex,
|
|
51623
|
-
});
|
|
51944
|
+
this.setNewPosition(cellId, sheet.id, colIndex - 1, rowIndex);
|
|
51624
51945
|
}
|
|
51625
51946
|
}
|
|
51626
51947
|
}
|
|
@@ -51630,7 +51951,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
51630
51951
|
* Move the cells after a column or rows insertion
|
|
51631
51952
|
*/
|
|
51632
51953
|
moveCellsOnAddition(sheet, addedElement, quantity, dimension) {
|
|
51633
|
-
const
|
|
51954
|
+
const updates = [];
|
|
51634
51955
|
for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
|
|
51635
51956
|
const row = sheet.rows[rowIndex];
|
|
51636
51957
|
if (dimension !== "rows" || rowIndex >= addedElement) {
|
|
@@ -51639,20 +51960,20 @@ class SheetPlugin extends CorePlugin {
|
|
|
51639
51960
|
const cellId = row.cells[i];
|
|
51640
51961
|
if (cellId) {
|
|
51641
51962
|
if (dimension === "rows" || colIndex >= addedElement) {
|
|
51642
|
-
|
|
51643
|
-
type: "UPDATE_CELL_POSITION",
|
|
51963
|
+
updates.push({
|
|
51644
51964
|
sheetId: sheet.id,
|
|
51645
51965
|
cellId: cellId,
|
|
51646
51966
|
col: colIndex + (dimension === "columns" ? quantity : 0),
|
|
51647
51967
|
row: rowIndex + (dimension === "rows" ? quantity : 0),
|
|
51968
|
+
type: "UPDATE_CELL_POSITION",
|
|
51648
51969
|
});
|
|
51649
51970
|
}
|
|
51650
51971
|
}
|
|
51651
51972
|
}
|
|
51652
51973
|
}
|
|
51653
51974
|
}
|
|
51654
|
-
for (let
|
|
51655
|
-
this.
|
|
51975
|
+
for (let update of updates.reverse()) {
|
|
51976
|
+
this.updateCellPosition(update);
|
|
51656
51977
|
}
|
|
51657
51978
|
}
|
|
51658
51979
|
/**
|
|
@@ -51685,12 +52006,7 @@ class SheetPlugin extends CorePlugin {
|
|
|
51685
52006
|
const colIndex = Number(i);
|
|
51686
52007
|
const cellId = row.cells[i];
|
|
51687
52008
|
if (cellId) {
|
|
51688
|
-
this.
|
|
51689
|
-
sheetId: sheet.id,
|
|
51690
|
-
cellId: cellId,
|
|
51691
|
-
col: colIndex,
|
|
51692
|
-
row: rowIndex - numberRows,
|
|
51693
|
-
});
|
|
52009
|
+
this.setNewPosition(cellId, sheet.id, colIndex, rowIndex - numberRows);
|
|
51694
52010
|
}
|
|
51695
52011
|
}
|
|
51696
52012
|
}
|
|
@@ -54456,6 +54772,9 @@ class Evaluator {
|
|
|
54456
54772
|
if (!this.blockedArrayFormulas.has(position)) {
|
|
54457
54773
|
this.invalidateSpreading(position);
|
|
54458
54774
|
}
|
|
54775
|
+
if (this.spreadingRelations.isArrayFormula(position)) {
|
|
54776
|
+
this.spreadingRelations.removeNode(position);
|
|
54777
|
+
}
|
|
54459
54778
|
const cell = this.getters.getCell(position);
|
|
54460
54779
|
if (cell === undefined) {
|
|
54461
54780
|
return EMPTY_CELL;
|
|
@@ -54496,7 +54815,6 @@ class Evaluator {
|
|
|
54496
54815
|
this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
|
|
54497
54816
|
const nbColumns = formulaReturn.length;
|
|
54498
54817
|
const nbRows = formulaReturn[0].length;
|
|
54499
|
-
this.spreadingRelations.removeNode(formulaPosition);
|
|
54500
54818
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
|
|
54501
54819
|
this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
|
|
54502
54820
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
|
|
@@ -58205,6 +58523,8 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
58205
58523
|
case "INSERT_PIVOT_WITH_TABLE":
|
|
58206
58524
|
this.insertPivotWithTable(cmd.sheetId, cmd.col, cmd.row, cmd.pivotId, cmd.table);
|
|
58207
58525
|
break;
|
|
58526
|
+
case "SPLIT_PIVOT_FORMULA":
|
|
58527
|
+
this.splitPivotFormula(cmd.sheetId, cmd.col, cmd.row, cmd.pivotId, cmd.table);
|
|
58208
58528
|
}
|
|
58209
58529
|
}
|
|
58210
58530
|
insertNewPivot(pivotId, sheetId) {
|
|
@@ -58330,6 +58650,36 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
58330
58650
|
});
|
|
58331
58651
|
}
|
|
58332
58652
|
}
|
|
58653
|
+
splitPivotFormula(sheetId, col, row, pivotId, pivotTableData) {
|
|
58654
|
+
this.dispatch("INSERT_PIVOT", {
|
|
58655
|
+
sheetId,
|
|
58656
|
+
col,
|
|
58657
|
+
row,
|
|
58658
|
+
pivotId,
|
|
58659
|
+
table: pivotTableData,
|
|
58660
|
+
});
|
|
58661
|
+
const table = this.getters.getCoreTable({ sheetId, col, row });
|
|
58662
|
+
if (table?.type === "dynamic") {
|
|
58663
|
+
const zone = positionToZone({ col, row });
|
|
58664
|
+
const { cols, rows, measures, fieldsType } = pivotTableData;
|
|
58665
|
+
const pivotTable = new SpreadsheetPivotTable(cols, rows, measures, fieldsType || {});
|
|
58666
|
+
const colNumber = pivotTable.getNumberOfDataColumns() + 1;
|
|
58667
|
+
const rowNumber = pivotTable.columns.length + pivotTable.rows.length;
|
|
58668
|
+
const tableZone = {
|
|
58669
|
+
left: col,
|
|
58670
|
+
top: row,
|
|
58671
|
+
right: col + colNumber - 1,
|
|
58672
|
+
bottom: row + rowNumber - 1,
|
|
58673
|
+
};
|
|
58674
|
+
const rangeData = this.getters.getRangeDataFromZone(sheetId, tableZone);
|
|
58675
|
+
this.dispatch("UPDATE_TABLE", {
|
|
58676
|
+
sheetId,
|
|
58677
|
+
zone,
|
|
58678
|
+
newTableRange: rangeData,
|
|
58679
|
+
tableType: "static",
|
|
58680
|
+
});
|
|
58681
|
+
}
|
|
58682
|
+
}
|
|
58333
58683
|
}
|
|
58334
58684
|
|
|
58335
58685
|
class SortPlugin extends UIPlugin {
|
|
@@ -63410,6 +63760,8 @@ css /* scss */ `
|
|
|
63410
63760
|
|
|
63411
63761
|
.o-sidePanel-handle-container {
|
|
63412
63762
|
width: 8px;
|
|
63763
|
+
position: fixed;
|
|
63764
|
+
top: 50%;
|
|
63413
63765
|
}
|
|
63414
63766
|
.o-sidePanel-handle {
|
|
63415
63767
|
cursor: col-resize;
|
|
@@ -63817,13 +64169,6 @@ class TopBarComposer extends owl.Component {
|
|
|
63817
64169
|
"border-color": SELECTION_BORDER_COLOR,
|
|
63818
64170
|
});
|
|
63819
64171
|
}
|
|
63820
|
-
get delimitation() {
|
|
63821
|
-
const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
|
|
63822
|
-
return {
|
|
63823
|
-
width,
|
|
63824
|
-
height,
|
|
63825
|
-
};
|
|
63826
|
-
}
|
|
63827
64172
|
onFocus(selection) {
|
|
63828
64173
|
this.composerFocusStore.focusComposer(this.composerInterface, { selection });
|
|
63829
64174
|
}
|
|
@@ -66045,7 +66390,7 @@ function createEmptyStructure(node) {
|
|
|
66045
66390
|
}
|
|
66046
66391
|
|
|
66047
66392
|
class StateObserver {
|
|
66048
|
-
changes
|
|
66393
|
+
changes;
|
|
66049
66394
|
commands = [];
|
|
66050
66395
|
/**
|
|
66051
66396
|
* Record the changes which could happen in the given callback, save them in a
|
|
@@ -66077,7 +66422,7 @@ class StateObserver {
|
|
|
66077
66422
|
if (value[key] === val) {
|
|
66078
66423
|
return;
|
|
66079
66424
|
}
|
|
66080
|
-
this.changes
|
|
66425
|
+
this.changes?.push({
|
|
66081
66426
|
key,
|
|
66082
66427
|
target: value,
|
|
66083
66428
|
before: value[key],
|
|
@@ -68744,6 +69089,6 @@ exports.tokenColors = tokenColors;
|
|
|
68744
69089
|
exports.tokenize = tokenize;
|
|
68745
69090
|
|
|
68746
69091
|
|
|
68747
|
-
__info__.version = "17.5.0-alpha.
|
|
68748
|
-
__info__.date = "2024-
|
|
68749
|
-
__info__.hash = "
|
|
69092
|
+
__info__.version = "17.5.0-alpha.3";
|
|
69093
|
+
__info__.date = "2024-08-02T08:24:53.425Z";
|
|
69094
|
+
__info__.hash = "767725f";
|