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