@odoo/o-spreadsheet 16.5.0-alpha.7 → 16.5.0-alpha.8

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.
@@ -1,3 +1,12 @@
1
+
2
+ /**
3
+ * This file is generated by o-spreadsheet build tools. Do not edit it.
4
+ * @see https://github.com/odoo/o-spreadsheet
5
+ * @version 16.5.0-alpha.8
6
+ * @date 2023-09-21T15:59:10.959Z
7
+ * @hash 0b7527d
8
+ */
9
+
1
10
  (function (exports, owl) {
2
11
  'use strict';
3
12
 
@@ -232,13 +241,26 @@
232
241
  const MENU_ITEM_PADDING_VERTICAL = 4;
233
242
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
234
243
  const MENU_SEPARATOR_PADDING = 5;
244
+ // Style
245
+ const DEFAULT_STYLE = {
246
+ align: "left",
247
+ verticalAlign: "bottom",
248
+ wrapping: "overflow",
249
+ bold: false,
250
+ italic: false,
251
+ strikethrough: false,
252
+ underline: false,
253
+ fontSize: 10,
254
+ fillColor: "",
255
+ textColor: "#000000",
256
+ };
257
+ const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
258
+ const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
235
259
  // Fonts
236
260
  const DEFAULT_FONT_WEIGHT = "400";
237
- const DEFAULT_FONT_SIZE = 10;
261
+ const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
238
262
  const HEADER_FONT_SIZE = 11;
239
263
  const DEFAULT_FONT = "'Roboto', arial";
240
- const DEFAULT_VERTICAL_ALIGN = "bottom";
241
- const DEFAULT_WRAPPING_MODE = "overflow";
242
264
  // Borders
243
265
  const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
244
266
  const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
@@ -616,13 +638,17 @@
616
638
  return position === "after" ? base + 1 : base;
617
639
  }
618
640
  /**
619
- * Compare two objects.
641
+ * Compares two objects.
620
642
  */
621
643
  function deepEquals(o1, o2) {
622
644
  if (o1 === o2)
623
645
  return true;
624
646
  if ((o1 && !o2) || (o2 && !o1))
625
647
  return false;
648
+ if (typeof o1 !== typeof o2)
649
+ return false;
650
+ if (typeof o1 !== "object")
651
+ return o1 === o2;
626
652
  // Objects can have different keys if the values are undefined
627
653
  const keys = new Set();
628
654
  Object.keys(o1).forEach((key) => keys.add(key));
@@ -738,6 +764,10 @@
738
764
  newArray.splice(index, 0, ...items);
739
765
  return newArray;
740
766
  }
767
+ function trimContent(content) {
768
+ const contentLines = content.split("\n");
769
+ return contentLines.map((line) => line.replace(/\s+/g, " ").trim()).join("\n");
770
+ }
741
771
 
742
772
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
743
773
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -4404,6 +4434,16 @@
4404
4434
  return 0;
4405
4435
  }
4406
4436
  }
4437
+ function tryCastAsNumberMatrix(data, argName) {
4438
+ data.forEach((row) => {
4439
+ row.forEach((cell) => {
4440
+ if (typeof cell !== "number") {
4441
+ throw new Error(_t("Function [[FUNCTION_NAME]] expects number values for %s, but got a %s.", typeof cell, argName));
4442
+ }
4443
+ });
4444
+ });
4445
+ return data;
4446
+ }
4407
4447
  function strictToNumber(value, locale) {
4408
4448
  if (value === "") {
4409
4449
  throw new Error(expectNumberValueError(value));
@@ -5348,6 +5388,7 @@
5348
5388
  padding: 10px;
5349
5389
  width: ${ERROR_TOOLTIP_WIDTH}px;
5350
5390
  box-sizing: border-box !important;
5391
+ overflow-wrap: break-word;
5351
5392
  }
5352
5393
  `;
5353
5394
  class ErrorToolTip extends owl.Component {
@@ -8353,43 +8394,53 @@
8353
8394
  TRUNC: TRUNC
8354
8395
  });
8355
8396
 
8356
- // Note: dataY and dataX may not have the same dimension
8357
- function covariance(dataY, dataX, isSample) {
8358
- let flatDataY = [];
8359
- let flatDataX = [];
8397
+ function assertSameNumberOfElements(...args) {
8398
+ const dims = args[0].length;
8399
+ 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())));
8400
+ }
8401
+
8402
+ function filterAndFlatData(dataY, dataX) {
8403
+ const _flatDataY = [];
8404
+ const _flatDataX = [];
8360
8405
  let lenY = 0;
8361
8406
  let lenX = 0;
8362
8407
  visitAny([dataY], (y) => {
8363
- flatDataY.push(y);
8408
+ _flatDataY.push(y);
8364
8409
  lenY += 1;
8365
8410
  });
8366
8411
  visitAny([dataX], (x) => {
8367
- flatDataX.push(x);
8412
+ _flatDataX.push(x);
8368
8413
  lenX += 1;
8369
8414
  });
8370
- assert(() => lenY === lenX, _t("[[FUNCTION_NAME]] has mismatched argument count %s vs %s.", lenY.toString(), lenX.toString()));
8371
- let count = 0;
8372
- let sumY = 0;
8373
- let sumX = 0;
8415
+ assert(() => lenY === lenX, _t("[[FUNCTION_NAME]] has mismatched argument count %s vs %s.", lenY, lenX));
8416
+ const flatDataX = [];
8417
+ const flatDataY = [];
8374
8418
  for (let i = 0; i < lenY; i++) {
8375
- const valueY = flatDataY[i];
8376
- const valueX = flatDataX[i];
8419
+ const valueY = _flatDataY[i];
8420
+ const valueX = _flatDataX[i];
8377
8421
  if (typeof valueY === "number" && typeof valueX === "number") {
8378
- count += 1;
8379
- sumY += valueY;
8380
- sumX += valueX;
8422
+ flatDataY.push(valueY);
8423
+ flatDataX.push(valueX);
8381
8424
  }
8382
8425
  }
8426
+ return { flatDataX, flatDataY };
8427
+ }
8428
+ // Note: dataY and dataX may not have the same dimension
8429
+ function covariance(dataY, dataX, isSample) {
8430
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
8431
+ const count = flatDataY.length;
8383
8432
  assert(() => count !== 0 && (!isSample || count !== 1), _t(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));
8433
+ let sumY = 0;
8434
+ let sumX = 0;
8435
+ for (let i = 0; i < count; i++) {
8436
+ sumY += flatDataY[i];
8437
+ sumX += flatDataX[i];
8438
+ }
8384
8439
  const averageY = sumY / count;
8385
8440
  const averageX = sumX / count;
8386
8441
  let acc = 0;
8387
- for (let i = 0; i < lenY; i++) {
8388
- const valueY = flatDataY[i];
8389
- const valueX = flatDataX[i];
8390
- if (typeof valueY === "number" && typeof valueX === "number") {
8391
- acc += (valueY - averageY) * (valueX - averageX);
8392
- }
8442
+ for (let i = 0; i < count; i++) {
8443
+ acc += (flatDataY[i] - averageY) * (flatDataX[i] - averageX);
8393
8444
  }
8394
8445
  return acc / (count - (isSample ? 1 : 0));
8395
8446
  }
@@ -8426,6 +8477,161 @@
8426
8477
  }
8427
8478
  return percentile(sortedArray, _percent, isInclusive);
8428
8479
  }
8480
+ function prepareDataForRegression(X, Y, newX) {
8481
+ const _X = X.length ? X : [range(1, Y.flat().length + 1)];
8482
+ const nVar = _X.length;
8483
+ let _newX = newX.length ? newX : _X;
8484
+ _newX = _newX.length === nVar ? transposeMatrix(_newX) : _newX;
8485
+ return { _X, _newX };
8486
+ }
8487
+ /*
8488
+ * This function performs a linear regression on the data set. It returns an array with two elements.
8489
+ * The first element is the slope, and the second element is the intercept.
8490
+ * The linear regression line is: y = slope*x + intercept
8491
+ * The function use the least squares method to find the best fit for the data set :
8492
+ * see https://www.mathsisfun.com/data/least-squares-regression.html
8493
+ * https://www.statology.org/standard-error-of-estimate/
8494
+ * https://agronomy4future.org/?p=16670
8495
+ * https://vitalflux.com/interpreting-f-statistics-in-linear-regression-formula-examples/
8496
+ * https://web.ist.utl.pt/~ist11038/compute/errtheory/,regression/regrthroughorigin.pdf
8497
+ */
8498
+ function fullLinearRegression(X, Y, computeIntercept = true, verbose = false) {
8499
+ const y = Y.flat();
8500
+ const n = y.length;
8501
+ let { _X } = prepareDataForRegression(X, Y, []);
8502
+ _X = _X.length === n ? transposeMatrix(_X) : _X.slice();
8503
+ assertSameNumberOfElements(_X[0], y);
8504
+ const nVar = _X.length;
8505
+ const nDeg = n - nVar - (computeIntercept ? 1 : 0);
8506
+ const yMatrix = [y];
8507
+ const xMatrix = transposeMatrix(_X.reverse());
8508
+ let avgX = [];
8509
+ for (let i = 0; i < nVar; i++) {
8510
+ avgX.push(0);
8511
+ if (computeIntercept) {
8512
+ for (const xij of _X[i]) {
8513
+ avgX[i] += xij;
8514
+ }
8515
+ avgX[i] /= n;
8516
+ }
8517
+ }
8518
+ let avgY = 0;
8519
+ if (computeIntercept) {
8520
+ for (const yi of y) {
8521
+ avgY += yi;
8522
+ }
8523
+ avgY /= n;
8524
+ }
8525
+ const redX = xMatrix.map((row) => row.map((value, i) => value - avgX[i]));
8526
+ if (computeIntercept) {
8527
+ xMatrix.forEach((row) => row.push(1));
8528
+ }
8529
+ const coeffs = getLMSCoefficients(xMatrix, yMatrix);
8530
+ if (!computeIntercept) {
8531
+ coeffs.push([0]);
8532
+ }
8533
+ if (!verbose) {
8534
+ return coeffs;
8535
+ }
8536
+ const dot1 = multiplyMatrices(redX, transposeMatrix(redX));
8537
+ const { inverted: dotInv } = invertMatrix(dot1);
8538
+ if (dotInv === undefined) {
8539
+ throw new Error(_t("Matrix is not invertible"));
8540
+ }
8541
+ let SSE = 0, SSR = 0;
8542
+ for (let i = 0; i < n; i++) {
8543
+ const yi = y[i] - avgY;
8544
+ let temp = 0;
8545
+ for (let j = 0; j < nVar; j++) {
8546
+ const xi = redX[i][j];
8547
+ temp += xi * coeffs[j][0];
8548
+ }
8549
+ const ei = yi - temp;
8550
+ SSE += ei * ei;
8551
+ SSR += temp * temp;
8552
+ }
8553
+ const RMSE = Math.sqrt(SSE / nDeg);
8554
+ const r2 = SSR / (SSR + SSE);
8555
+ const f_stat = SSR / nVar / (SSE / nDeg);
8556
+ const deltaCoeffs = [];
8557
+ for (let i = 0; i < nVar; i++) {
8558
+ deltaCoeffs.push(RMSE * Math.sqrt(dotInv[i][i]));
8559
+ }
8560
+ if (computeIntercept) {
8561
+ const dot2 = multiplyMatrices(dotInv, [avgX]);
8562
+ const dot3 = multiplyMatrices(transposeMatrix([avgX]), dot2);
8563
+ deltaCoeffs.push(RMSE * Math.sqrt(dot3[0][0] + 1 / y.length));
8564
+ }
8565
+ const returned = [
8566
+ [coeffs[0][0], deltaCoeffs[0], r2, f_stat, SSR],
8567
+ [coeffs[1][0], deltaCoeffs[1], RMSE, nDeg, SSE],
8568
+ ];
8569
+ for (let i = 2; i < nVar; i++) {
8570
+ returned.push([coeffs[i][0], deltaCoeffs[i], "", "", ""]);
8571
+ }
8572
+ if (computeIntercept) {
8573
+ returned.push([coeffs[nVar][0], deltaCoeffs[nVar], "", "", ""]);
8574
+ }
8575
+ else {
8576
+ returned.push([0, "", "", "", ""]);
8577
+ }
8578
+ return returned;
8579
+ }
8580
+ /*
8581
+ This function performs a polynomial regression on the data set. It returns the coefficients of
8582
+ the polynomial function that best fits the data set.
8583
+ The polynomial function is: y = c0 + c1*x + c2*x^2 + ... + cn*x^n, where n is the order (degree)
8584
+ of the polynomial. The returned coefficients are then in the form: [c0, c1, c2, ..., cn]
8585
+ The function is based on the method of least squares :
8586
+ see: https://mathworld.wolfram.com/LeastSquaresFittingPolynomial.html
8587
+ */
8588
+ function polynomialRegression(flatY, flatX, order, intercept) {
8589
+ assertSameNumberOfElements(flatX, flatY);
8590
+ assert(() => order >= 1, _t(`Function [[FUNCTION_NAME]] A regression of order less than 1 cannot be possible.`));
8591
+ const yMatrix = [flatY];
8592
+ const xMatrix = flatX.map((x) => range(0, order).map((i) => Math.pow(x, order - i)));
8593
+ if (intercept) {
8594
+ xMatrix.forEach((row) => row.push(1));
8595
+ }
8596
+ const coeffs = getLMSCoefficients(xMatrix, yMatrix);
8597
+ if (!intercept) {
8598
+ coeffs.push([0]);
8599
+ }
8600
+ return coeffs;
8601
+ }
8602
+ function getLMSCoefficients(xMatrix, yMatrix) {
8603
+ const xMatrixT = transposeMatrix(xMatrix);
8604
+ const dot1 = multiplyMatrices(xMatrix, xMatrixT);
8605
+ const { inverted: dotInv } = invertMatrix(dot1);
8606
+ if (dotInv === undefined) {
8607
+ throw new Error(_t("Matrix is not invertible"));
8608
+ }
8609
+ const dot2 = multiplyMatrices(xMatrix, yMatrix);
8610
+ return transposeMatrix(multiplyMatrices(dotInv, dot2));
8611
+ }
8612
+ function evaluatePolynomial(coeffs, x, order) {
8613
+ return coeffs.reduce((acc, coeff, i) => acc + coeff * Math.pow(x, order - i), 0);
8614
+ }
8615
+ function expM(M) {
8616
+ return M.map((col) => col.map((cell) => Math.exp(cell)));
8617
+ }
8618
+ function logM(M) {
8619
+ return M.map((col) => col.map((cell) => Math.log(cell)));
8620
+ }
8621
+ function predictLinearValues(Y, X, newX, computeIntercept) {
8622
+ const { _X, _newX } = prepareDataForRegression(X, Y, newX);
8623
+ const coeffs = fullLinearRegression(_X, Y, computeIntercept, false);
8624
+ const nVar = coeffs.length - 1;
8625
+ const newY = _newX.map((col) => {
8626
+ let value = 0;
8627
+ for (let i = 0; i < nVar; i++) {
8628
+ value += coeffs[i][0] * col[nVar - i - 1];
8629
+ }
8630
+ value += coeffs[nVar][0];
8631
+ return [value];
8632
+ });
8633
+ return newY.length === newX.length ? newY : transposeMatrix(newY);
8634
+ }
8429
8635
  // -----------------------------------------------------------------------------
8430
8636
  // AVEDEV
8431
8637
  // -----------------------------------------------------------------------------
@@ -8710,6 +8916,56 @@
8710
8916
  isExported: true,
8711
8917
  };
8712
8918
  // -----------------------------------------------------------------------------
8919
+ // FORECAST
8920
+ // -----------------------------------------------------------------------------
8921
+ const FORECAST = {
8922
+ description: _t("Calculates the expected y-value for a specified x based on a linear regression of a dataset."),
8923
+ args: [
8924
+ arg("x (number, range<number>)", _t("The value(s) on the x-axis to forecast.")),
8925
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
8926
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
8927
+ ],
8928
+ returns: ["NUMBER"],
8929
+ compute: function (x, dataY, dataX) {
8930
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
8931
+ return predictLinearValues([flatDataY], [flatDataX], matrixMap(toMatrix(x), (value) => toNumber(value, this.locale)), true);
8932
+ },
8933
+ isExported: true,
8934
+ };
8935
+ // -----------------------------------------------------------------------------
8936
+ // GROWTH
8937
+ // -----------------------------------------------------------------------------
8938
+ const GROWTH = {
8939
+ description: _t("Fits points to exponential growth trend."),
8940
+ args: [
8941
+ arg("known_data_y (range<number>)", _t("The array or range containing dependent (y) values that are already known, used to curve fit an ideal exponential growth curve.")),
8942
+ arg("known_data_x (range<number>, default={1;2;3;...})", _t("The values of the independent variable(s) corresponding with known_data_y.")),
8943
+ arg("new_data_x (any, range, default=known_data_x)", _t("The data points to return the y values for on the ideal curve fit.")),
8944
+ arg("b (boolean, default=TRUE)", _t("Given a general exponential form of y = b*m^x for a curve fit, calculates b if TRUE or forces b to be 1 and only calculates the m values if FALSE.")),
8945
+ ],
8946
+ returns: ["NUMBER"],
8947
+ compute: function (knownDataY, knownDataX = [], newDataX = [], b = true) {
8948
+ return expM(predictLinearValues(logM(tryCastAsNumberMatrix(knownDataY, "the first argument (known_data_y)")), tryCastAsNumberMatrix(knownDataX, "the second argument (known_data_x)"), tryCastAsNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b)));
8949
+ },
8950
+ };
8951
+ // -----------------------------------------------------------------------------
8952
+ // INTERCEPT
8953
+ // -----------------------------------------------------------------------------
8954
+ const INTERCEPT = {
8955
+ description: _t("Compute the intercept of the linear regression."),
8956
+ args: [
8957
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
8958
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
8959
+ ],
8960
+ returns: ["NUMBER"],
8961
+ compute: function (dataY, dataX) {
8962
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
8963
+ const [[], [intercept]] = fullLinearRegression([flatDataX], [flatDataY]);
8964
+ return intercept;
8965
+ },
8966
+ isExported: true,
8967
+ };
8968
+ // -----------------------------------------------------------------------------
8713
8969
  // LARGE
8714
8970
  // -----------------------------------------------------------------------------
8715
8971
  const LARGE = {
@@ -8737,12 +8993,94 @@
8737
8993
  });
8738
8994
  const result = largests.shift();
8739
8995
  assert(() => result !== undefined, _t(`[[FUNCTION_NAME]] has no valid input data.`));
8740
- assert(() => count >= _n, _t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n.toString()));
8996
+ assert(() => count >= _n, _t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n));
8741
8997
  return result;
8742
8998
  },
8743
8999
  isExported: true,
8744
9000
  };
8745
9001
  // -----------------------------------------------------------------------------
9002
+ // LINEST
9003
+ // -----------------------------------------------------------------------------
9004
+ const LINEST = {
9005
+ description: _t("Compute the intercept of the linear regression."),
9006
+ args: [
9007
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9008
+ arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
9009
+ arg("calculate_b (boolean, default=TRUE)", _t("A flag specifying wheter to compute the slope or not")),
9010
+ arg("verbose (boolean, default=FALSE)", _t("A flag specifying whether to return additional regression statistics or only the linear coefficients and the y-intercept")),
9011
+ ],
9012
+ returns: ["NUMBER"],
9013
+ compute: function (dataY, dataX = [], calculateB = true, verbose = false) {
9014
+ return fullLinearRegression(tryCastAsNumberMatrix(dataX, "the first argument (data_y)"), tryCastAsNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
9015
+ },
9016
+ isExported: true,
9017
+ };
9018
+ // -----------------------------------------------------------------------------
9019
+ // LOGEST
9020
+ // -----------------------------------------------------------------------------
9021
+ const LOGEST = {
9022
+ description: _t("Compute the intercept of the linear regression."),
9023
+ args: [
9024
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9025
+ arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
9026
+ arg("calculate_b (boolean, default=TRUE)", _t("A flag specifying wheter to compute the slope or not")),
9027
+ arg("verbose (boolean, default=FALSE)", _t("A flag specifying whether to return additional regression statistics or only the linear coefficients and the y-intercept")),
9028
+ ],
9029
+ returns: ["NUMBER"],
9030
+ compute: function (dataY, dataX = [], calculateB = true, verbose = false) {
9031
+ const coeffs = fullLinearRegression(tryCastAsNumberMatrix(dataX, "the second argument (data_x)"), logM(tryCastAsNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
9032
+ for (let i = 0; i < coeffs.length; i++) {
9033
+ coeffs[i][0] = Math.exp(coeffs[i][0]);
9034
+ }
9035
+ return coeffs;
9036
+ },
9037
+ isExported: true,
9038
+ };
9039
+ // -----------------------------------------------------------------------------
9040
+ // MATTHEWS
9041
+ // -----------------------------------------------------------------------------
9042
+ const MATTHEWS = {
9043
+ description: _t("Compute the Matthews correlation coefficient of a dataset."),
9044
+ args: [
9045
+ arg("data_x (range)", _t("The range representing the array or matrix of observed data.")),
9046
+ arg("data_y (range)", _t("The range representing the array or matrix of predicted data.")),
9047
+ ],
9048
+ returns: ["NUMBER"],
9049
+ compute: function (dataX, dataY) {
9050
+ const flatX = dataX.flat();
9051
+ const flatY = dataY.flat();
9052
+ assertSameNumberOfElements(flatX, flatY);
9053
+ if (flatX.length === 0) {
9054
+ throw new Error(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
9055
+ }
9056
+ const n = flatX.length;
9057
+ let trueN = 0, trueP = 0, falseP = 0, falseN = 0;
9058
+ for (let i = 0; i < n; ++i) {
9059
+ const isTrue1 = toBoolean(flatX[i]);
9060
+ const isTrue2 = toBoolean(flatY[i]);
9061
+ if (isTrue1 === isTrue2) {
9062
+ if (isTrue1) {
9063
+ trueP++;
9064
+ }
9065
+ else {
9066
+ trueN++;
9067
+ }
9068
+ }
9069
+ else {
9070
+ if (isTrue1) {
9071
+ falseN++;
9072
+ }
9073
+ else {
9074
+ falseP++;
9075
+ }
9076
+ }
9077
+ }
9078
+ return ((trueP * trueN - falseP * falseN) /
9079
+ Math.sqrt((trueP + falseP) * (trueP + falseN) * (trueN + falseP) * (trueN + falseN)));
9080
+ },
9081
+ isExported: false,
9082
+ };
9083
+ // -----------------------------------------------------------------------------
8746
9084
  // MAX
8747
9085
  // -----------------------------------------------------------------------------
8748
9086
  const MAX = {
@@ -8897,6 +9235,41 @@
8897
9235
  isExported: true,
8898
9236
  };
8899
9237
  // -----------------------------------------------------------------------------
9238
+ // PEARSON
9239
+ // -----------------------------------------------------------------------------
9240
+ const PEARSON = {
9241
+ description: _t("Compute the Pearson product-moment correlation coefficient of a dataset."),
9242
+ args: [
9243
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9244
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9245
+ ],
9246
+ returns: ["NUMBER"],
9247
+ compute: function (dataY, dataX) {
9248
+ const { flatDataX, flatDataY } = filterAndFlatData(dataX, dataY);
9249
+ if (flatDataX.length === 0) {
9250
+ throw new Error(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
9251
+ }
9252
+ if (flatDataX.length < 2) {
9253
+ throw new Error(_t("[[FUNCTION_NAME]] needs at least two values for both parameters"));
9254
+ }
9255
+ const n = flatDataX.length;
9256
+ let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0, sumYY = 0;
9257
+ for (let i = 0; i < n; i++) {
9258
+ const xij = flatDataX[i];
9259
+ const yij = flatDataY[i];
9260
+ sumX += xij;
9261
+ sumY += yij;
9262
+ sumXY += xij * yij;
9263
+ sumXX += xij * xij;
9264
+ sumYY += yij * yij;
9265
+ }
9266
+ return ((n * sumXY - sumX * sumY) / Math.sqrt((n * sumXX - sumX * sumX) * (n * sumYY - sumY * sumY)));
9267
+ },
9268
+ isExported: true,
9269
+ };
9270
+ // In GSheet, CORREL is just an alias to PEARSON
9271
+ const CORREL = PEARSON;
9272
+ // -----------------------------------------------------------------------------
8900
9273
  // PERCENTILE
8901
9274
  // -----------------------------------------------------------------------------
8902
9275
  const PERCENTILE = {
@@ -8951,6 +9324,45 @@
8951
9324
  isExported: true,
8952
9325
  };
8953
9326
  // -----------------------------------------------------------------------------
9327
+ // POLYFIT
9328
+ // -----------------------------------------------------------------------------
9329
+ const POLYFIT_COEFFS = {
9330
+ description: _t("Compute the coefficients of polynomial regression of the dataset."),
9331
+ args: [
9332
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9333
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9334
+ arg("order (number)", _t("The order of the polynomial to fit the data, between 1 and 6.")),
9335
+ arg("intercept (boolean, default=TRUE)", _t("A flag specifying whether to compute the intercept or not.")),
9336
+ ],
9337
+ returns: ["RANGE<NUMBER>"],
9338
+ compute: function (dataY, dataX, order, intercept = true) {
9339
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
9340
+ return polynomialRegression(flatDataY, flatDataX, toNumber(order, this.locale), toBoolean(intercept));
9341
+ },
9342
+ isExported: false,
9343
+ };
9344
+ // -----------------------------------------------------------------------------
9345
+ // POLYFIT.FORECAST
9346
+ // -----------------------------------------------------------------------------
9347
+ const POLYFIT_FORECAST = {
9348
+ description: _t("Predict value by computing a polynomial regression of the dataset."),
9349
+ args: [
9350
+ arg("x (number, range<number>)", _t("The value(s) on the x-axis to forecast.")),
9351
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9352
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9353
+ arg("order (number)", _t("The order of the polynomial to fit the data, between 1 and 6.")),
9354
+ arg("intercept (boolean, default=TRUE)", _t("A flag specifying whether to compute the intercept or not.")),
9355
+ ],
9356
+ returns: ["NUMBER"],
9357
+ compute: function (x, dataY, dataX, order, intercept = true) {
9358
+ const _order = toNumber(order, this.locale);
9359
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
9360
+ const coeffs = polynomialRegression(flatDataY, flatDataX, _order, toBoolean(intercept)).flat();
9361
+ return matrixMap(toMatrix(x), (xij) => evaluatePolynomial(coeffs, toNumber(xij, this.locale), _order));
9362
+ },
9363
+ isExported: false,
9364
+ };
9365
+ // -----------------------------------------------------------------------------
8954
9366
  // QUARTILE
8955
9367
  // -----------------------------------------------------------------------------
8956
9368
  const QUARTILE = {
@@ -9007,6 +9419,39 @@
9007
9419
  isExported: true,
9008
9420
  };
9009
9421
  // -----------------------------------------------------------------------------
9422
+ // RSQ
9423
+ // -----------------------------------------------------------------------------
9424
+ const RSQ = {
9425
+ description: _t("Compute the square of r, the Pearson product-moment correlation coefficient of a dataset."),
9426
+ args: [
9427
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9428
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9429
+ ],
9430
+ returns: ["NUMBER"],
9431
+ compute: function (dataY, dataX) {
9432
+ const pearson = PEARSON.compute.bind(this);
9433
+ return Math.pow(pearson(dataX, dataY), 2.0);
9434
+ },
9435
+ isExported: true,
9436
+ };
9437
+ // -----------------------------------------------------------------------------
9438
+ // SLOPE
9439
+ // -----------------------------------------------------------------------------
9440
+ const SLOPE = {
9441
+ description: _t("Compute the slope of the linear regression."),
9442
+ args: [
9443
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9444
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9445
+ ],
9446
+ returns: ["NUMBER"],
9447
+ compute: function (dataY, dataX) {
9448
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
9449
+ const [[slope]] = fullLinearRegression([flatDataX], [flatDataY]);
9450
+ return slope;
9451
+ },
9452
+ isExported: true,
9453
+ };
9454
+ // -----------------------------------------------------------------------------
9010
9455
  // SMALL
9011
9456
  // -----------------------------------------------------------------------------
9012
9457
  const SMALL = {
@@ -9034,12 +9479,39 @@
9034
9479
  });
9035
9480
  const result = largests.pop();
9036
9481
  assert(() => result !== undefined, _t(`[[FUNCTION_NAME]] has no valid input data.`));
9037
- assert(() => count >= _n, _t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n.toString()));
9482
+ assert(() => count >= _n, _t("Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.", _n));
9038
9483
  return result;
9039
9484
  },
9040
9485
  isExported: true,
9041
9486
  };
9042
9487
  // -----------------------------------------------------------------------------
9488
+ // SPEARMAN
9489
+ // -----------------------------------------------------------------------------
9490
+ const SPEARMAN = {
9491
+ description: _t("Compute the Spearman rank correlation coefficient of a dataset."),
9492
+ args: [
9493
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9494
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9495
+ ],
9496
+ returns: ["NUMBER"],
9497
+ compute: function (dataX, dataY) {
9498
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
9499
+ const n = flatDataX.length;
9500
+ const order = flatDataX.map((e, i) => [e, flatDataY[i]]);
9501
+ order.sort((a, b) => a[0] - b[0]);
9502
+ for (let i = 0; i < n; ++i) {
9503
+ order[i][0] = i;
9504
+ }
9505
+ order.sort((a, b) => a[1] - b[1]);
9506
+ let sum = 0.0;
9507
+ for (let i = 0; i < n; ++i) {
9508
+ sum += (order[i][0] - i) ** 2;
9509
+ }
9510
+ return 1 - (6 * sum) / (n ** 3 - n);
9511
+ },
9512
+ isExported: false,
9513
+ };
9514
+ // -----------------------------------------------------------------------------
9043
9515
  // STDEV
9044
9516
  // -----------------------------------------------------------------------------
9045
9517
  const STDEV = {
@@ -9130,6 +9602,39 @@
9130
9602
  isExported: true,
9131
9603
  };
9132
9604
  // -----------------------------------------------------------------------------
9605
+ // STEYX
9606
+ // -----------------------------------------------------------------------------
9607
+ const STEYX = {
9608
+ description: _t("Calculates the standard error of the predicted y-value for each x in the regression of a dataset."),
9609
+ args: [
9610
+ arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
9611
+ arg("data_x (range<number>)", _t("The range representing the array or matrix of independent data.")),
9612
+ ],
9613
+ returns: ["NUMBER"],
9614
+ compute: function (dataY, dataX) {
9615
+ const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
9616
+ const data = fullLinearRegression([flatDataX], [flatDataY], true, true);
9617
+ return data[1][2];
9618
+ },
9619
+ isExported: true,
9620
+ };
9621
+ // -----------------------------------------------------------------------------
9622
+ // TREND
9623
+ // -----------------------------------------------------------------------------
9624
+ const TREND = {
9625
+ description: _t("Fits points to linear trend derived via least-squares."),
9626
+ args: [
9627
+ arg("known_data_y (number, range<number>)", _t("The array or range containing dependent (y) values that are already known, used to curve fit an ideal linear trend.")),
9628
+ arg("known_data_x (number, range<number>, optional, default={1;2;3;...})", _t("The values of the independent variable(s) corresponding with known_data_y.")),
9629
+ arg("new_data_x (number, range<number>, optional, default=known_data_x)", _t("The data points to return the y values for on the ideal curve fit.")),
9630
+ arg("b (boolean, optional, default=TRUE)", _t("Given a general linear form of y = m*x+b for a curve fit, calculates b if TRUE or forces b to be 0 and only calculates the m values if FALSE, i.e. forces the curve fit to pass through the origin.")),
9631
+ ],
9632
+ returns: ["NUMBER"],
9633
+ compute: function (knownDataY, knownDataX = [], newDataX = [], b = true) {
9634
+ return predictLinearValues(tryCastAsNumberMatrix(knownDataY, "the first argument (known_data_y)"), tryCastAsNumberMatrix(knownDataX, "the second argument (known_data_x)"), tryCastAsNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b));
9635
+ },
9636
+ };
9637
+ // -----------------------------------------------------------------------------
9133
9638
  // VAR
9134
9639
  // -----------------------------------------------------------------------------
9135
9640
  const VAR = {
@@ -9228,12 +9733,19 @@
9228
9733
  AVERAGEIF: AVERAGEIF,
9229
9734
  AVERAGEIFS: AVERAGEIFS,
9230
9735
  AVERAGE_WEIGHTED: AVERAGE_WEIGHTED,
9736
+ CORREL: CORREL,
9231
9737
  COUNT: COUNT,
9232
9738
  COUNTA: COUNTA,
9233
9739
  COVAR: COVAR,
9234
9740
  COVARIANCE_P: COVARIANCE_P,
9235
9741
  COVARIANCE_S: COVARIANCE_S,
9742
+ FORECAST: FORECAST,
9743
+ GROWTH: GROWTH,
9744
+ INTERCEPT: INTERCEPT,
9236
9745
  LARGE: LARGE,
9746
+ LINEST: LINEST,
9747
+ LOGEST: LOGEST,
9748
+ MATTHEWS: MATTHEWS,
9237
9749
  MAX: MAX,
9238
9750
  MAXA: MAXA,
9239
9751
  MAXIFS: MAXIFS,
@@ -9241,19 +9753,27 @@
9241
9753
  MIN: MIN,
9242
9754
  MINA: MINA,
9243
9755
  MINIFS: MINIFS,
9756
+ PEARSON: PEARSON,
9244
9757
  PERCENTILE: PERCENTILE,
9245
9758
  PERCENTILE_EXC: PERCENTILE_EXC,
9246
9759
  PERCENTILE_INC: PERCENTILE_INC,
9760
+ POLYFIT_COEFFS: POLYFIT_COEFFS,
9761
+ POLYFIT_FORECAST: POLYFIT_FORECAST,
9247
9762
  QUARTILE: QUARTILE,
9248
9763
  QUARTILE_EXC: QUARTILE_EXC,
9249
9764
  QUARTILE_INC: QUARTILE_INC,
9765
+ RSQ: RSQ,
9766
+ SLOPE: SLOPE,
9250
9767
  SMALL: SMALL,
9768
+ SPEARMAN: SPEARMAN,
9251
9769
  STDEV: STDEV,
9252
9770
  STDEVA: STDEVA,
9253
9771
  STDEVP: STDEVP,
9254
9772
  STDEVPA: STDEVPA,
9255
9773
  STDEV_P: STDEV_P,
9256
9774
  STDEV_S: STDEV_S,
9775
+ STEYX: STEYX,
9776
+ TREND: TREND,
9257
9777
  VAR: VAR,
9258
9778
  VARA: VARA,
9259
9779
  VARP: VARP,
@@ -13888,7 +14408,7 @@
13888
14408
  ],
13889
14409
  returns: ["STRING"],
13890
14410
  compute: function (text) {
13891
- return toString(text).trim();
14411
+ return trimContent(toString(text));
13892
14412
  },
13893
14413
  isExported: true,
13894
14414
  };
@@ -19437,6 +19957,12 @@
19437
19957
  env.openSidePanel("RemoveDuplicates", {});
19438
19958
  },
19439
19959
  };
19960
+ const trimWhitespace = {
19961
+ name: _t("Trim whitespace"),
19962
+ execute: (env) => {
19963
+ env.model.dispatch("TRIM_WHITESPACE");
19964
+ },
19965
+ };
19440
19966
  const sortDescending = {
19441
19967
  name: _t("Descending (Z ⟶ A)"),
19442
19968
  execute: (env) => {
@@ -20796,6 +21322,10 @@
20796
21322
  .addChild("remove_duplicates", ["data", "data_cleanup"], {
20797
21323
  ...removeDuplicates,
20798
21324
  sequence: 10,
21325
+ })
21326
+ .addChild("trim_whitespace", ["data", "data_cleanup"], {
21327
+ ...trimWhitespace,
21328
+ sequence: 20,
20799
21329
  })
20800
21330
  .addChild("split_to_columns", ["data"], {
20801
21331
  ...splitToColumns,
@@ -25971,9 +26501,11 @@
25971
26501
  }
25972
26502
  }
25973
26503
  function updateMousePosition(e) {
25974
- x = e.offsetX;
25975
- y = e.offsetY;
25976
- lastMoved = Date.now();
26504
+ if (gridRef.el === e.target) {
26505
+ x = e.offsetX;
26506
+ y = e.offsetY;
26507
+ lastMoved = Date.now();
26508
+ }
25977
26509
  }
25978
26510
  function recompute() {
25979
26511
  const { col, row } = getPosition();
@@ -32455,8 +32987,9 @@
32455
32987
  for (const position of positions) {
32456
32988
  const cell = this.getters.getCell(position);
32457
32989
  const xc = toXC(position.col, position.row);
32990
+ const style = this.removeDefaultStyleValues(cell.style);
32458
32991
  cells[xc] = {
32459
- style: cell.style ? getItemId(cell.style, styles) : undefined,
32992
+ style: Object.keys(style).length ? getItemId(style, styles) : undefined,
32460
32993
  format: cell.format ? getItemId(cell.format, formats) : undefined,
32461
32994
  content: cell.content || undefined,
32462
32995
  };
@@ -32475,6 +33008,15 @@
32475
33008
  exportForExcel(data) {
32476
33009
  this.export(data);
32477
33010
  }
33011
+ removeDefaultStyleValues(style) {
33012
+ const cleanedStyle = { ...style };
33013
+ for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
33014
+ if (cleanedStyle[property] === defaultValue) {
33015
+ delete cleanedStyle[property];
33016
+ }
33017
+ }
33018
+ return cleanedStyle;
33019
+ }
32478
33020
  // ---------------------------------------------------------------------------
32479
33021
  // GETTERS
32480
33022
  // ---------------------------------------------------------------------------
@@ -39813,6 +40355,7 @@
39813
40355
  cells;
39814
40356
  copiedTables;
39815
40357
  zones;
40358
+ uuidGenerator = new UuidGenerator();
39816
40359
  constructor(zones, operation, getters, dispatch, selection) {
39817
40360
  super(operation, getters, dispatch, selection);
39818
40361
  if (!zones.length) {
@@ -40265,8 +40808,9 @@
40265
40808
  this.adaptCFRules(origin.sheetId, cf, [xc], toRemoveRange);
40266
40809
  }
40267
40810
  else {
40268
- this.adaptCFRules(target.sheetId, cf, [xc], []);
40269
40811
  this.adaptCFRules(origin.sheetId, cf, [], toRemoveRange);
40812
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, cf);
40813
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [xc], []);
40270
40814
  }
40271
40815
  }
40272
40816
  }
@@ -40290,6 +40834,12 @@
40290
40834
  sheetId,
40291
40835
  });
40292
40836
  }
40837
+ getCFToCopyTo(targetSheetId, originCF) {
40838
+ const cfInTarget = this.getters
40839
+ .getConditionalFormats(targetSheetId)
40840
+ .find((cf) => cf.stopIfTrue === originCF.stopIfTrue && deepEquals(cf.rule, originCF.rule));
40841
+ return cfInTarget ? cfInTarget : { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
40842
+ }
40293
40843
  }
40294
40844
 
40295
40845
  class DataCleanupPlugin extends UIPlugin {
@@ -40308,6 +40858,9 @@
40308
40858
  case "REMOVE_DUPLICATES":
40309
40859
  this.removeDuplicates(cmd.columns, cmd.hasHeader);
40310
40860
  break;
40861
+ case "TRIM_WHITESPACE":
40862
+ this.trimWhitespace();
40863
+ break;
40311
40864
  }
40312
40865
  }
40313
40866
  // ---------------------------------------------------------------------------
@@ -40428,6 +40981,35 @@
40428
40981
  }
40429
40982
  return "Success" /* CommandResult.Success */;
40430
40983
  }
40984
+ trimWhitespace() {
40985
+ const zones = this.getters.getSelectedZones();
40986
+ const sheetId = this.getters.getActiveSheetId();
40987
+ let count = 0;
40988
+ for (const { col, row } of zones.map(positions).flat()) {
40989
+ const cell = this.getters.getCell({ col, row, sheetId });
40990
+ if (!cell) {
40991
+ continue;
40992
+ }
40993
+ const trimmedContent = trimContent(cell.content);
40994
+ if (trimmedContent !== cell.content) {
40995
+ count += 1;
40996
+ this.dispatch("UPDATE_CELL", {
40997
+ sheetId,
40998
+ col,
40999
+ row,
41000
+ content: trimmedContent,
41001
+ });
41002
+ }
41003
+ }
41004
+ const text = count
41005
+ ? _t("Trimmed whitespace from %s cells.", count)
41006
+ : _t("No selected cells had whitespace trimmed.");
41007
+ this.ui.notifyUI({
41008
+ type: "info",
41009
+ text: text,
41010
+ sticky: false,
41011
+ });
41012
+ }
40431
41013
  }
40432
41014
 
40433
41015
  const BORDER_COLOR = "#8B008B";
@@ -43226,7 +43808,7 @@
43226
43808
  allowDispatch(cmd) {
43227
43809
  switch (cmd.type) {
43228
43810
  case "CUT":
43229
- const zones = cmd.cutTarget || this.getters.getSelectedZones();
43811
+ const zones = this.getters.getSelectedZones();
43230
43812
  const state = this.getClipboardState(zones, cmd.type);
43231
43813
  return state.isCutAllowed(zones);
43232
43814
  case "PASTE":
@@ -43261,7 +43843,7 @@
43261
43843
  switch (cmd.type) {
43262
43844
  case "COPY":
43263
43845
  case "CUT":
43264
- const zones = ("cutTarget" in cmd && cmd.cutTarget) || this.getters.getSelectedZones();
43846
+ const zones = this.getters.getSelectedZones();
43265
43847
  this.state = this.getClipboardState(zones, cmd.type);
43266
43848
  this.status = "visible";
43267
43849
  this.originSheetId = this.getters.getActiveSheetId();
@@ -44925,26 +45507,24 @@
44925
45507
  const isBasedBefore = cmd.base < start;
44926
45508
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
44927
45509
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
44928
- this.dispatch("CUT", {
44929
- cutTarget: [
44930
- {
44931
- left: isCol ? start + deltaCol : 0,
44932
- right: isCol ? end + deltaCol : this.getters.getNumberCols(cmd.sheetId) - 1,
44933
- top: !isCol ? start + deltaRow : 0,
44934
- bottom: !isCol ? end + deltaRow : this.getters.getNumberRows(cmd.sheetId) - 1,
44935
- },
44936
- ],
44937
- });
44938
- this.dispatch("PASTE", {
44939
- target: [
44940
- {
44941
- left: isCol ? cmd.base : 0,
44942
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
44943
- top: !isCol ? cmd.base : 0,
44944
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
44945
- },
44946
- ],
44947
- });
45510
+ const target = [
45511
+ {
45512
+ left: isCol ? start + deltaCol : 0,
45513
+ right: isCol ? end + deltaCol : this.getters.getNumberCols(cmd.sheetId) - 1,
45514
+ top: !isCol ? start + deltaRow : 0,
45515
+ bottom: !isCol ? end + deltaRow : this.getters.getNumberRows(cmd.sheetId) - 1,
45516
+ },
45517
+ ];
45518
+ const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
45519
+ const pasteTarget = [
45520
+ {
45521
+ left: isCol ? cmd.base : 0,
45522
+ right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
45523
+ top: !isCol ? cmd.base : 0,
45524
+ bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
45525
+ },
45526
+ ];
45527
+ state.paste(pasteTarget, { selectTarget: true });
44948
45528
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
44949
45529
  let currentIndex = cmd.base;
44950
45530
  for (const element of toRemove) {
@@ -52293,9 +52873,9 @@
52293
52873
  exports.tokenize = tokenize;
52294
52874
 
52295
52875
 
52296
- __info__.version = '16.5.0-alpha.7';
52297
- __info__.date = '2023-09-12T11:43:41.584Z';
52298
- __info__.hash = '';
52876
+ __info__.version = "16.5.0-alpha.8";
52877
+ __info__.date = "2023-09-21T15:59:10.959Z";
52878
+ __info__.hash = "0b7527d";
52299
52879
 
52300
52880
 
52301
52881
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);