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