@odoo/o-spreadsheet 18.2.0 → 18.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o-spreadsheet.cjs.js +999 -791
- package/dist/o-spreadsheet.d.ts +33 -15
- package/dist/o-spreadsheet.esm.js +999 -791
- package/dist/o-spreadsheet.iife.js +999 -791
- package/dist/o-spreadsheet.iife.min.js +400 -379
- package/dist/o_spreadsheet.xml +31 -26
- package/package.json +2 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 18.2.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.2.2
|
|
6
|
+
* @date 2025-03-07T10:41:04.411Z
|
|
7
|
+
* @hash f567932
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
|
|
@@ -4458,7 +4458,7 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
|
|
|
4458
4458
|
* @param reverseSearch if true, search in the array starting from the end.
|
|
4459
4459
|
|
|
4460
4460
|
*/
|
|
4461
|
-
function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {
|
|
4461
|
+
function linearSearch(data, target, mode, numberOfValues, getValueInData, lookupCaches, reverseSearch = false) {
|
|
4462
4462
|
if (target === undefined || target.value === null) {
|
|
4463
4463
|
return -1;
|
|
4464
4464
|
}
|
|
@@ -4467,17 +4467,48 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
|
|
|
4467
4467
|
}
|
|
4468
4468
|
const _target = normalizeValue(target.value);
|
|
4469
4469
|
const getValue = reverseSearch
|
|
4470
|
-
? (data, i) => getValueInData(data, numberOfValues - i - 1)
|
|
4471
|
-
: getValueInData;
|
|
4470
|
+
? (data, i) => normalizeValue(getValueInData(data, numberOfValues - i - 1))
|
|
4471
|
+
: (data, i) => normalizeValue(getValueInData(data, i));
|
|
4472
|
+
// first check if the target is in the cache
|
|
4473
|
+
const isNotWildcardTarget = mode !== "wildcard" ||
|
|
4474
|
+
typeof _target !== "string" ||
|
|
4475
|
+
!(_target.includes("*") || _target.includes("?"));
|
|
4476
|
+
if (lookupCaches && isNotWildcardTarget) {
|
|
4477
|
+
const searchMode = reverseSearch ? "reverseSearch" : "forwardSearch";
|
|
4478
|
+
let cache = lookupCaches[searchMode].get(data);
|
|
4479
|
+
if (cache === undefined) {
|
|
4480
|
+
// build the cache for all the values
|
|
4481
|
+
cache = new Map();
|
|
4482
|
+
for (let i = 0; i < numberOfValues; i++) {
|
|
4483
|
+
const value = getValue(data, i) ?? null;
|
|
4484
|
+
if (!cache.has(value)) {
|
|
4485
|
+
cache.set(value, i);
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
lookupCaches[searchMode].set(data, cache);
|
|
4489
|
+
}
|
|
4490
|
+
if (cache.has(_target)) {
|
|
4491
|
+
const resultIndex = cache.get(_target);
|
|
4492
|
+
return reverseSearch ? numberOfValues - resultIndex - 1 : resultIndex;
|
|
4493
|
+
}
|
|
4494
|
+
if (mode === "strict") {
|
|
4495
|
+
return -1;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
// else perform the linear search
|
|
4499
|
+
const resultIndex = _linearSearch(data, _target, mode, numberOfValues, getValue);
|
|
4500
|
+
return reverseSearch && resultIndex !== -1 ? numberOfValues - resultIndex - 1 : resultIndex;
|
|
4501
|
+
}
|
|
4502
|
+
function _linearSearch(data, _target, mode, numberOfValues, getNormalizeValue) {
|
|
4472
4503
|
let indexMatchTarget = (i) => {
|
|
4473
|
-
return
|
|
4504
|
+
return getNormalizeValue(data, i) === _target;
|
|
4474
4505
|
};
|
|
4475
4506
|
if (mode === "wildcard" &&
|
|
4476
4507
|
typeof _target === "string" &&
|
|
4477
4508
|
(_target.includes("*") || _target.includes("?"))) {
|
|
4478
4509
|
const regExp = wildcardToRegExp(_target);
|
|
4479
4510
|
indexMatchTarget = (i) => {
|
|
4480
|
-
const value =
|
|
4511
|
+
const value = getNormalizeValue(data, i);
|
|
4481
4512
|
if (typeof value === "string") {
|
|
4482
4513
|
return regExp.test(value);
|
|
4483
4514
|
}
|
|
@@ -4488,7 +4519,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
|
|
|
4488
4519
|
let closestMatchIndex = -1;
|
|
4489
4520
|
if (mode === "nextSmaller") {
|
|
4490
4521
|
indexMatchTarget = (i) => {
|
|
4491
|
-
const value =
|
|
4522
|
+
const value = getNormalizeValue(data, i);
|
|
4492
4523
|
if ((!closestMatch && compareCellValues(_target, value) >= 0) ||
|
|
4493
4524
|
(compareCellValues(_target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {
|
|
4494
4525
|
closestMatch = value;
|
|
@@ -4499,7 +4530,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
|
|
|
4499
4530
|
}
|
|
4500
4531
|
if (mode === "nextGreater") {
|
|
4501
4532
|
indexMatchTarget = (i) => {
|
|
4502
|
-
const value =
|
|
4533
|
+
const value = getNormalizeValue(data, i);
|
|
4503
4534
|
if ((!closestMatch && compareCellValues(_target, value) <= 0) ||
|
|
4504
4535
|
(compareCellValues(_target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {
|
|
4505
4536
|
closestMatch = value;
|
|
@@ -4510,12 +4541,10 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
|
|
|
4510
4541
|
}
|
|
4511
4542
|
for (let i = 0; i < numberOfValues; i++) {
|
|
4512
4543
|
if (indexMatchTarget(i)) {
|
|
4513
|
-
return
|
|
4544
|
+
return i;
|
|
4514
4545
|
}
|
|
4515
4546
|
}
|
|
4516
|
-
return
|
|
4517
|
-
? numberOfValues - closestMatchIndex - 1
|
|
4518
|
-
: closestMatchIndex;
|
|
4547
|
+
return closestMatchIndex;
|
|
4519
4548
|
}
|
|
4520
4549
|
/**
|
|
4521
4550
|
* Normalize a value.
|
|
@@ -6071,8 +6100,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6071
6100
|
if (zone.bottom !== zone.top && zone.left != zone.right) {
|
|
6072
6101
|
if (zone.right) {
|
|
6073
6102
|
for (let j = zone.left; j <= zone.right; ++j) {
|
|
6103
|
+
const datasetOptions = j === zone.left ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6074
6104
|
postProcessedRanges.push({
|
|
6075
|
-
...
|
|
6105
|
+
...datasetOptions,
|
|
6076
6106
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6077
6107
|
left: j,
|
|
6078
6108
|
right: j,
|
|
@@ -6084,8 +6114,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6084
6114
|
}
|
|
6085
6115
|
else {
|
|
6086
6116
|
for (let j = zone.top; j <= zone.bottom; ++j) {
|
|
6117
|
+
const datasetOptions = j === zone.top ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6087
6118
|
postProcessedRanges.push({
|
|
6088
|
-
...
|
|
6119
|
+
...datasetOptions,
|
|
6089
6120
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6090
6121
|
left: zone.left,
|
|
6091
6122
|
right: zone.right,
|
|
@@ -6489,10 +6520,11 @@ class UuidGenerator {
|
|
|
6489
6520
|
*
|
|
6490
6521
|
*/
|
|
6491
6522
|
smallUuid() {
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6523
|
+
if (window.crypto) {
|
|
6524
|
+
return "10000000-1000".replace(/[01]/g, (c) => {
|
|
6525
|
+
const n = Number(c);
|
|
6526
|
+
return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
|
|
6527
|
+
});
|
|
6496
6528
|
}
|
|
6497
6529
|
else {
|
|
6498
6530
|
// mainly for jest and other browsers that do not have the crypto functionality
|
|
@@ -6507,10 +6539,11 @@ class UuidGenerator {
|
|
|
6507
6539
|
* This method should be used when you need to avoid collisions at all costs, like the id of a revision.
|
|
6508
6540
|
*/
|
|
6509
6541
|
uuidv4() {
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6542
|
+
if (window.crypto) {
|
|
6543
|
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
|
|
6544
|
+
const n = Number(c);
|
|
6545
|
+
return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
|
|
6546
|
+
});
|
|
6514
6547
|
}
|
|
6515
6548
|
else {
|
|
6516
6549
|
// mainly for jest and other browsers that do not have the crypto functionality
|
|
@@ -9501,150 +9534,6 @@ class ComposerFocusStore extends SpreadsheetStore {
|
|
|
9501
9534
|
}
|
|
9502
9535
|
}
|
|
9503
9536
|
|
|
9504
|
-
/**
|
|
9505
|
-
* This file is largely inspired by owl 1.
|
|
9506
|
-
* `css` tag has been removed from owl 2 without workaround to manage css.
|
|
9507
|
-
* So, the solution was to import the behavior of owl 1 directly in our
|
|
9508
|
-
* codebase, with one difference: the css is added to the sheet as soon as the
|
|
9509
|
-
* css tag is executed. In owl 1, the css was added as soon as a Component was
|
|
9510
|
-
* created for the first time.
|
|
9511
|
-
*/
|
|
9512
|
-
const STYLESHEETS = {};
|
|
9513
|
-
let nextId = 0;
|
|
9514
|
-
/**
|
|
9515
|
-
* CSS tag helper for defining inline stylesheets. With this, one can simply define
|
|
9516
|
-
* an inline stylesheet with just the following code:
|
|
9517
|
-
* ```js
|
|
9518
|
-
* css`.component-a { color: red; }`;
|
|
9519
|
-
* ```
|
|
9520
|
-
*/
|
|
9521
|
-
function css(strings, ...args) {
|
|
9522
|
-
const name = `__sheet__${nextId++}`;
|
|
9523
|
-
const value = String.raw(strings, ...args);
|
|
9524
|
-
registerSheet(name, value);
|
|
9525
|
-
activateSheet(name);
|
|
9526
|
-
return name;
|
|
9527
|
-
}
|
|
9528
|
-
function processSheet(str) {
|
|
9529
|
-
const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
|
|
9530
|
-
const selectorStack = [];
|
|
9531
|
-
const parts = [];
|
|
9532
|
-
let rules = [];
|
|
9533
|
-
function generateSelector(stackIndex, parentSelector) {
|
|
9534
|
-
const parts = [];
|
|
9535
|
-
for (const selector of selectorStack[stackIndex]) {
|
|
9536
|
-
let part = (parentSelector && parentSelector + " " + selector) || selector;
|
|
9537
|
-
if (part.includes("&")) {
|
|
9538
|
-
part = selector.replace(/&/g, parentSelector || "");
|
|
9539
|
-
}
|
|
9540
|
-
if (stackIndex < selectorStack.length - 1) {
|
|
9541
|
-
part = generateSelector(stackIndex + 1, part);
|
|
9542
|
-
}
|
|
9543
|
-
parts.push(part);
|
|
9544
|
-
}
|
|
9545
|
-
return parts.join(", ");
|
|
9546
|
-
}
|
|
9547
|
-
function generateRules() {
|
|
9548
|
-
if (rules.length) {
|
|
9549
|
-
parts.push(generateSelector(0) + " {");
|
|
9550
|
-
parts.push(...rules);
|
|
9551
|
-
parts.push("}");
|
|
9552
|
-
rules = [];
|
|
9553
|
-
}
|
|
9554
|
-
}
|
|
9555
|
-
while (tokens.length) {
|
|
9556
|
-
let token = tokens.shift();
|
|
9557
|
-
if (token === "}") {
|
|
9558
|
-
generateRules();
|
|
9559
|
-
selectorStack.pop();
|
|
9560
|
-
}
|
|
9561
|
-
else {
|
|
9562
|
-
if (tokens[0] === "{") {
|
|
9563
|
-
generateRules();
|
|
9564
|
-
selectorStack.push(token.split(/\s*,\s*/));
|
|
9565
|
-
tokens.shift();
|
|
9566
|
-
}
|
|
9567
|
-
if (tokens[0] === ";") {
|
|
9568
|
-
rules.push(" " + token + ";");
|
|
9569
|
-
}
|
|
9570
|
-
}
|
|
9571
|
-
}
|
|
9572
|
-
return parts.join("\n");
|
|
9573
|
-
}
|
|
9574
|
-
function registerSheet(id, css) {
|
|
9575
|
-
const sheet = document.createElement("style");
|
|
9576
|
-
sheet.textContent = processSheet(css);
|
|
9577
|
-
STYLESHEETS[id] = sheet;
|
|
9578
|
-
}
|
|
9579
|
-
function activateSheet(id) {
|
|
9580
|
-
const sheet = STYLESHEETS[id];
|
|
9581
|
-
sheet.setAttribute("component", id);
|
|
9582
|
-
document.head.appendChild(sheet);
|
|
9583
|
-
}
|
|
9584
|
-
function getTextDecoration({ strikethrough, underline, }) {
|
|
9585
|
-
if (!strikethrough && !underline) {
|
|
9586
|
-
return "none";
|
|
9587
|
-
}
|
|
9588
|
-
return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
|
|
9589
|
-
}
|
|
9590
|
-
/**
|
|
9591
|
-
* Convert the cell style to CSS properties.
|
|
9592
|
-
*/
|
|
9593
|
-
function cellStyleToCss(style) {
|
|
9594
|
-
const attributes = cellTextStyleToCss(style);
|
|
9595
|
-
if (!style)
|
|
9596
|
-
return attributes;
|
|
9597
|
-
if (style.fillColor) {
|
|
9598
|
-
attributes["background"] = style.fillColor;
|
|
9599
|
-
}
|
|
9600
|
-
return attributes;
|
|
9601
|
-
}
|
|
9602
|
-
/**
|
|
9603
|
-
* Convert the cell text style to CSS properties.
|
|
9604
|
-
*/
|
|
9605
|
-
function cellTextStyleToCss(style) {
|
|
9606
|
-
const attributes = {};
|
|
9607
|
-
if (!style)
|
|
9608
|
-
return attributes;
|
|
9609
|
-
if (style.bold) {
|
|
9610
|
-
attributes["font-weight"] = "bold";
|
|
9611
|
-
}
|
|
9612
|
-
if (style.italic) {
|
|
9613
|
-
attributes["font-style"] = "italic";
|
|
9614
|
-
}
|
|
9615
|
-
if (style.strikethrough || style.underline) {
|
|
9616
|
-
let decoration = style.strikethrough ? "line-through" : "";
|
|
9617
|
-
decoration = style.underline ? decoration + " underline" : decoration;
|
|
9618
|
-
attributes["text-decoration"] = decoration;
|
|
9619
|
-
}
|
|
9620
|
-
if (style.textColor) {
|
|
9621
|
-
attributes["color"] = style.textColor;
|
|
9622
|
-
}
|
|
9623
|
-
return attributes;
|
|
9624
|
-
}
|
|
9625
|
-
/**
|
|
9626
|
-
* Transform CSS properties into a CSS string.
|
|
9627
|
-
*/
|
|
9628
|
-
function cssPropertiesToCss(attributes) {
|
|
9629
|
-
let styleStr = "";
|
|
9630
|
-
for (const attName in attributes) {
|
|
9631
|
-
if (!attributes[attName]) {
|
|
9632
|
-
continue;
|
|
9633
|
-
}
|
|
9634
|
-
styleStr += `${attName}:${attributes[attName]}; `;
|
|
9635
|
-
}
|
|
9636
|
-
return styleStr;
|
|
9637
|
-
}
|
|
9638
|
-
function getElementMargins(el) {
|
|
9639
|
-
const style = window.getComputedStyle(el);
|
|
9640
|
-
return {
|
|
9641
|
-
top: parseInt(style.marginTop, 10) || 0,
|
|
9642
|
-
bottom: parseInt(style.marginBottom, 10) || 0,
|
|
9643
|
-
left: parseInt(style.marginLeft, 10) || 0,
|
|
9644
|
-
right: parseInt(style.marginRight, 10) || 0,
|
|
9645
|
-
};
|
|
9646
|
-
}
|
|
9647
|
-
|
|
9648
9537
|
const TREND_LINE_XAXIS_ID = "x1";
|
|
9649
9538
|
/**
|
|
9650
9539
|
* This file contains helpers that are common to different charts (mainly
|
|
@@ -10189,79 +10078,341 @@ function getNextNonEmptyBar(bars, startIndex) {
|
|
|
10189
10078
|
return bars.find((bar, i) => i > startIndex && bar.height !== 0);
|
|
10190
10079
|
}
|
|
10191
10080
|
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10081
|
+
const GAUGE_PADDING_SIDE = 30;
|
|
10082
|
+
const GAUGE_PADDING_TOP = 10;
|
|
10083
|
+
const GAUGE_PADDING_BOTTOM = 20;
|
|
10084
|
+
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
10085
|
+
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
10086
|
+
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
10087
|
+
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
10088
|
+
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
10089
|
+
function drawGaugeChart(canvas, runtime) {
|
|
10090
|
+
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
10091
|
+
canvas.width = canvasBoundingRect.width;
|
|
10092
|
+
canvas.height = canvasBoundingRect.height;
|
|
10093
|
+
const ctx = canvas.getContext("2d");
|
|
10094
|
+
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
10095
|
+
drawBackground(ctx, config);
|
|
10096
|
+
drawGauge(ctx, config);
|
|
10097
|
+
drawInflectionValues(ctx, config);
|
|
10098
|
+
drawLabels(ctx, config);
|
|
10099
|
+
drawTitle(ctx, config);
|
|
10100
|
+
}
|
|
10101
|
+
function drawGauge(ctx, config) {
|
|
10102
|
+
ctx.save();
|
|
10103
|
+
const gauge = config.gauge;
|
|
10104
|
+
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
10105
|
+
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
10106
|
+
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
10107
|
+
if (arcRadius < 0) {
|
|
10108
|
+
return;
|
|
10200
10109
|
}
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10110
|
+
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
10111
|
+
// Gauge background
|
|
10112
|
+
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
10113
|
+
ctx.beginPath();
|
|
10114
|
+
ctx.lineWidth = gauge.arcWidth;
|
|
10115
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
10116
|
+
ctx.stroke();
|
|
10117
|
+
// Gauge value
|
|
10118
|
+
ctx.strokeStyle = gauge.color;
|
|
10119
|
+
ctx.beginPath();
|
|
10120
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
10121
|
+
ctx.stroke();
|
|
10122
|
+
ctx.restore();
|
|
10123
|
+
}
|
|
10124
|
+
function drawBackground(ctx, config) {
|
|
10125
|
+
ctx.save();
|
|
10126
|
+
ctx.fillStyle = config.backgroundColor;
|
|
10127
|
+
ctx.fillRect(0, 0, config.width, config.height);
|
|
10128
|
+
ctx.restore();
|
|
10129
|
+
}
|
|
10130
|
+
function drawLabels(ctx, config) {
|
|
10131
|
+
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
10132
|
+
ctx.save();
|
|
10133
|
+
ctx.textAlign = "center";
|
|
10134
|
+
ctx.fillStyle = label.color;
|
|
10135
|
+
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
10136
|
+
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
10137
|
+
ctx.restore();
|
|
10138
|
+
}
|
|
10139
|
+
}
|
|
10140
|
+
function drawInflectionValues(ctx, config) {
|
|
10141
|
+
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
10142
|
+
for (const inflectionValue of config.inflectionValues) {
|
|
10143
|
+
ctx.save();
|
|
10144
|
+
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
10145
|
+
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
10146
|
+
ctx.lineWidth = 2;
|
|
10147
|
+
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
10148
|
+
ctx.beginPath();
|
|
10149
|
+
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
10150
|
+
ctx.lineTo(0, -height - 3);
|
|
10151
|
+
ctx.stroke();
|
|
10152
|
+
ctx.textAlign = "center";
|
|
10153
|
+
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
10154
|
+
ctx.fillStyle = inflectionValue.color;
|
|
10155
|
+
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
10156
|
+
ctx.fillText(inflectionValue.label, 0, textY);
|
|
10157
|
+
ctx.restore();
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
function drawTitle(ctx, config) {
|
|
10161
|
+
ctx.save();
|
|
10162
|
+
const title = config.title;
|
|
10163
|
+
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
10164
|
+
ctx.textBaseline = "middle";
|
|
10165
|
+
ctx.fillStyle = title.color;
|
|
10166
|
+
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
10167
|
+
ctx.restore();
|
|
10168
|
+
}
|
|
10169
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
10170
|
+
const maxValue = runtime.maxValue;
|
|
10171
|
+
const minValue = runtime.minValue;
|
|
10172
|
+
const gaugeValue = runtime.gaugeValue;
|
|
10173
|
+
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
10174
|
+
const gaugeArcWidth = gaugeRect.width / 6;
|
|
10175
|
+
const gaugePercentage = gaugeValue
|
|
10176
|
+
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
10177
|
+
: 0;
|
|
10178
|
+
const gaugeValuePosition = {
|
|
10179
|
+
x: boundingRect.width / 2,
|
|
10180
|
+
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
10207
10181
|
};
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
return this.chartRuntime.background;
|
|
10182
|
+
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
10183
|
+
// Scale down the font size if the gaugeRect is too small
|
|
10184
|
+
if (gaugeRect.height < 300) {
|
|
10185
|
+
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
10213
10186
|
}
|
|
10214
|
-
|
|
10215
|
-
|
|
10187
|
+
// Scale down the font size if the text is too long
|
|
10188
|
+
const maxTextWidth = gaugeRect.width / 2;
|
|
10189
|
+
const gaugeLabel = gaugeValue?.label || "-";
|
|
10190
|
+
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
10191
|
+
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
10216
10192
|
}
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10193
|
+
const minLabelPosition = {
|
|
10194
|
+
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
10195
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10196
|
+
};
|
|
10197
|
+
const maxLabelPosition = {
|
|
10198
|
+
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
10199
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10200
|
+
};
|
|
10201
|
+
const textColor = chartMutedFontColor(runtime.background);
|
|
10202
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
10203
|
+
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
10204
|
+
if (runtime.title.text) {
|
|
10205
|
+
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
10223
10206
|
}
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10207
|
+
switch (runtime.title.align) {
|
|
10208
|
+
case "right":
|
|
10209
|
+
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
10210
|
+
break;
|
|
10211
|
+
case "center":
|
|
10212
|
+
x = (boundingRect.width - titleWidth) / 2;
|
|
10213
|
+
break;
|
|
10214
|
+
case "left":
|
|
10215
|
+
default:
|
|
10216
|
+
x = CHART_PADDING$1;
|
|
10217
|
+
break;
|
|
10218
|
+
}
|
|
10219
|
+
return {
|
|
10220
|
+
width: boundingRect.width,
|
|
10221
|
+
height: boundingRect.height,
|
|
10222
|
+
title: {
|
|
10223
|
+
label: runtime.title.text ?? "",
|
|
10224
|
+
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
10225
|
+
textPosition: {
|
|
10226
|
+
x,
|
|
10227
|
+
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
10228
|
+
},
|
|
10229
|
+
color: runtime.title.color ?? textColor,
|
|
10230
|
+
bold: runtime.title.bold,
|
|
10231
|
+
italic: runtime.title.italic,
|
|
10232
|
+
},
|
|
10233
|
+
backgroundColor: runtime.background,
|
|
10234
|
+
gauge: {
|
|
10235
|
+
rect: gaugeRect,
|
|
10236
|
+
arcWidth: gaugeArcWidth,
|
|
10237
|
+
percentage: clip(gaugePercentage, 0, 1),
|
|
10238
|
+
color: getGaugeColor(runtime),
|
|
10239
|
+
},
|
|
10240
|
+
inflectionValues,
|
|
10241
|
+
gaugeValue: {
|
|
10242
|
+
label: gaugeLabel,
|
|
10243
|
+
textPosition: gaugeValuePosition,
|
|
10244
|
+
fontSize: gaugeValueFontSize,
|
|
10245
|
+
color: textColor,
|
|
10246
|
+
},
|
|
10247
|
+
minLabel: {
|
|
10248
|
+
label: runtime.minValue.label,
|
|
10249
|
+
textPosition: minLabelPosition,
|
|
10250
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10251
|
+
color: textColor,
|
|
10252
|
+
},
|
|
10253
|
+
maxLabel: {
|
|
10254
|
+
label: runtime.maxValue.label,
|
|
10255
|
+
textPosition: maxLabelPosition,
|
|
10256
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10257
|
+
color: textColor,
|
|
10258
|
+
},
|
|
10259
|
+
};
|
|
10260
|
+
}
|
|
10261
|
+
/**
|
|
10262
|
+
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
10263
|
+
* space for the title and labels.
|
|
10264
|
+
*/
|
|
10265
|
+
function getGaugeRect(boundingRect, title) {
|
|
10266
|
+
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
10267
|
+
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
10268
|
+
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
10269
|
+
let gaugeWidth;
|
|
10270
|
+
let gaugeHeight;
|
|
10271
|
+
if (drawWidth > 2 * drawHeight) {
|
|
10272
|
+
gaugeWidth = 2 * drawHeight;
|
|
10273
|
+
gaugeHeight = drawHeight;
|
|
10274
|
+
}
|
|
10275
|
+
else {
|
|
10276
|
+
gaugeWidth = drawWidth;
|
|
10277
|
+
gaugeHeight = drawWidth / 2;
|
|
10278
|
+
}
|
|
10279
|
+
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
10280
|
+
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
10281
|
+
return {
|
|
10282
|
+
x: gaugeX,
|
|
10283
|
+
y: gaugeY,
|
|
10284
|
+
width: gaugeWidth,
|
|
10285
|
+
height: gaugeHeight,
|
|
10286
|
+
};
|
|
10287
|
+
}
|
|
10288
|
+
/**
|
|
10289
|
+
* Get the infliction values of the gauge, and where to draw them (the angle from the center of the gauge at which they are drawn).
|
|
10290
|
+
*
|
|
10291
|
+
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
10292
|
+
*/
|
|
10293
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
10294
|
+
const maxValue = runtime.maxValue;
|
|
10295
|
+
const minValue = runtime.minValue;
|
|
10296
|
+
const gaugeCircleCenter = {
|
|
10297
|
+
x: gaugeRect.x + gaugeRect.width / 2,
|
|
10298
|
+
y: gaugeRect.y + gaugeRect.height,
|
|
10299
|
+
};
|
|
10300
|
+
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
10301
|
+
const inflectionValues = [];
|
|
10302
|
+
const inflectionValuesTextRects = [];
|
|
10303
|
+
for (const inflectionValue of runtime.inflectionValues) {
|
|
10304
|
+
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
10305
|
+
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
10306
|
+
const angle = Math.PI - Math.PI * percentage;
|
|
10307
|
+
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
10308
|
+
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
10309
|
+
gaugeCircleCenter.x, // center of the gauge circle
|
|
10310
|
+
gaugeCircleCenter.y, // center of the gauge circle
|
|
10311
|
+
labelWidth + 2, // width of the text + some margin
|
|
10312
|
+
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
10313
|
+
);
|
|
10314
|
+
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
10315
|
+
? GAUGE_LABELS_FONT_SIZE
|
|
10316
|
+
: 0;
|
|
10317
|
+
inflectionValuesTextRects.push(textRect);
|
|
10318
|
+
inflectionValues.push({
|
|
10319
|
+
rotation: angle,
|
|
10320
|
+
label: inflectionValue.label,
|
|
10321
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10322
|
+
color: textColor,
|
|
10323
|
+
offset,
|
|
10244
10324
|
});
|
|
10245
10325
|
}
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10326
|
+
return inflectionValues;
|
|
10327
|
+
}
|
|
10328
|
+
function getGaugeColor(runtime) {
|
|
10329
|
+
const gaugeValue = runtime.gaugeValue?.value;
|
|
10330
|
+
if (gaugeValue === undefined) {
|
|
10331
|
+
return GAUGE_BACKGROUND_COLOR;
|
|
10250
10332
|
}
|
|
10251
|
-
|
|
10252
|
-
const
|
|
10253
|
-
if (
|
|
10254
|
-
|
|
10255
|
-
if (chartData.options?.plugins?.title) {
|
|
10256
|
-
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
10257
|
-
}
|
|
10333
|
+
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
10334
|
+
const inflectionValue = runtime.inflectionValues[i];
|
|
10335
|
+
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
10336
|
+
return runtime.colors[i];
|
|
10258
10337
|
}
|
|
10259
|
-
else {
|
|
10260
|
-
|
|
10338
|
+
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
10339
|
+
return runtime.colors[i];
|
|
10261
10340
|
}
|
|
10262
|
-
this.chart.config.options = chartData.options;
|
|
10263
|
-
this.chart.update();
|
|
10264
10341
|
}
|
|
10342
|
+
return runtime.colors.at(-1);
|
|
10343
|
+
}
|
|
10344
|
+
function getSegmentsOfRectangle(rectangle) {
|
|
10345
|
+
return [
|
|
10346
|
+
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
10347
|
+
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
10348
|
+
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
10349
|
+
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
10350
|
+
];
|
|
10351
|
+
}
|
|
10352
|
+
/**
|
|
10353
|
+
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
10354
|
+
* is not handled.
|
|
10355
|
+
*/
|
|
10356
|
+
function doSegmentIntersect(segment1, segment2) {
|
|
10357
|
+
const A = segment1.start;
|
|
10358
|
+
const B = segment1.end;
|
|
10359
|
+
const C = segment2.start;
|
|
10360
|
+
const D = segment2.end;
|
|
10361
|
+
/**
|
|
10362
|
+
* Line segment intersection algorithm
|
|
10363
|
+
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
10364
|
+
*/
|
|
10365
|
+
function ccw(a, b, c) {
|
|
10366
|
+
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
10367
|
+
}
|
|
10368
|
+
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
10369
|
+
}
|
|
10370
|
+
function doRectanglesIntersect(rect1, rect2) {
|
|
10371
|
+
const segments1 = getSegmentsOfRectangle(rect1);
|
|
10372
|
+
const segments2 = getSegmentsOfRectangle(rect2);
|
|
10373
|
+
for (const segment1 of segments1) {
|
|
10374
|
+
for (const segment2 of segments2) {
|
|
10375
|
+
if (doSegmentIntersect(segment1, segment2)) {
|
|
10376
|
+
return true;
|
|
10377
|
+
}
|
|
10378
|
+
}
|
|
10379
|
+
}
|
|
10380
|
+
return false;
|
|
10381
|
+
}
|
|
10382
|
+
/**
|
|
10383
|
+
* Get the rectangle that is tangent to a circle at a given angle.
|
|
10384
|
+
*
|
|
10385
|
+
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
10386
|
+
*/
|
|
10387
|
+
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
10388
|
+
const cos = Math.cos(angle);
|
|
10389
|
+
const sin = Math.sin(angle);
|
|
10390
|
+
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
10391
|
+
const x = cos * radius;
|
|
10392
|
+
const y = sin * radius;
|
|
10393
|
+
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
10394
|
+
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
10395
|
+
const y2 = cos * (rectWidth / 2);
|
|
10396
|
+
const bottomRight = {
|
|
10397
|
+
x: x + x2 + circleCenterX,
|
|
10398
|
+
y: circleCenterY - (y - y2),
|
|
10399
|
+
};
|
|
10400
|
+
const bottomLeft = {
|
|
10401
|
+
x: x - x2 + circleCenterX,
|
|
10402
|
+
y: circleCenterY - (y + y2),
|
|
10403
|
+
};
|
|
10404
|
+
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
10405
|
+
const xp = cos * (radius + rectHeight);
|
|
10406
|
+
const yp = sin * (radius + rectHeight);
|
|
10407
|
+
const topLeft = {
|
|
10408
|
+
x: xp - x2 + circleCenterX,
|
|
10409
|
+
y: circleCenterY - (yp + y2),
|
|
10410
|
+
};
|
|
10411
|
+
const topRight = {
|
|
10412
|
+
x: xp + x2 + circleCenterX,
|
|
10413
|
+
y: circleCenterY - (yp - y2),
|
|
10414
|
+
};
|
|
10415
|
+
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
10265
10416
|
}
|
|
10266
10417
|
|
|
10267
10418
|
/**
|
|
@@ -10843,6 +10994,299 @@ class ScorecardChartConfigBuilder {
|
|
|
10843
10994
|
}
|
|
10844
10995
|
}
|
|
10845
10996
|
|
|
10997
|
+
const CHART_COMMON_OPTIONS = {
|
|
10998
|
+
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
10999
|
+
responsive: true, // will resize when its container is resized
|
|
11000
|
+
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
11001
|
+
elements: {
|
|
11002
|
+
line: {
|
|
11003
|
+
fill: false, // do not fill the area under line charts
|
|
11004
|
+
},
|
|
11005
|
+
point: {
|
|
11006
|
+
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
11007
|
+
},
|
|
11008
|
+
},
|
|
11009
|
+
animation: false,
|
|
11010
|
+
};
|
|
11011
|
+
function chartToImage(runtime, figure, type) {
|
|
11012
|
+
// wrap the canvas in a div with a fixed size because chart.js would
|
|
11013
|
+
// fill the whole page otherwise
|
|
11014
|
+
const div = document.createElement("div");
|
|
11015
|
+
div.style.width = `${figure.width}px`;
|
|
11016
|
+
div.style.height = `${figure.height}px`;
|
|
11017
|
+
const canvas = document.createElement("canvas");
|
|
11018
|
+
div.append(canvas);
|
|
11019
|
+
canvas.setAttribute("width", figure.width.toString());
|
|
11020
|
+
canvas.setAttribute("height", figure.height.toString());
|
|
11021
|
+
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
11022
|
+
document.body.append(div);
|
|
11023
|
+
if ("chartJsConfig" in runtime) {
|
|
11024
|
+
const config = deepCopy(runtime.chartJsConfig);
|
|
11025
|
+
config.plugins = [backgroundColorChartJSPlugin];
|
|
11026
|
+
const Chart = getChartJSConstructor();
|
|
11027
|
+
const chart = new Chart(canvas, config);
|
|
11028
|
+
const imgContent = chart.toBase64Image();
|
|
11029
|
+
chart.destroy();
|
|
11030
|
+
div.remove();
|
|
11031
|
+
return imgContent;
|
|
11032
|
+
}
|
|
11033
|
+
else if (type === "scorecard") {
|
|
11034
|
+
const design = getScorecardConfiguration(figure, runtime);
|
|
11035
|
+
drawScoreChart(design, canvas);
|
|
11036
|
+
const imgContent = canvas.toDataURL();
|
|
11037
|
+
div.remove();
|
|
11038
|
+
return imgContent;
|
|
11039
|
+
}
|
|
11040
|
+
else if (type === "gauge") {
|
|
11041
|
+
drawGaugeChart(canvas, runtime);
|
|
11042
|
+
const imgContent = canvas.toDataURL();
|
|
11043
|
+
div.remove();
|
|
11044
|
+
return imgContent;
|
|
11045
|
+
}
|
|
11046
|
+
return undefined;
|
|
11047
|
+
}
|
|
11048
|
+
/**
|
|
11049
|
+
* Custom chart.js plugin to set the background color of the canvas
|
|
11050
|
+
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
11051
|
+
*/
|
|
11052
|
+
const backgroundColorChartJSPlugin = {
|
|
11053
|
+
id: "customCanvasBackgroundColor",
|
|
11054
|
+
beforeDraw: (chart) => {
|
|
11055
|
+
const { ctx } = chart;
|
|
11056
|
+
ctx.save();
|
|
11057
|
+
ctx.globalCompositeOperation = "destination-over";
|
|
11058
|
+
ctx.fillStyle = "#ffffff";
|
|
11059
|
+
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
11060
|
+
ctx.restore();
|
|
11061
|
+
},
|
|
11062
|
+
};
|
|
11063
|
+
/** Return window.Chart, making sure all our extensions are loaded in ChartJS */
|
|
11064
|
+
function getChartJSConstructor() {
|
|
11065
|
+
if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
|
|
11066
|
+
window.Chart.register(chartShowValuesPlugin);
|
|
11067
|
+
window.Chart.register(waterfallLinesPlugin);
|
|
11068
|
+
}
|
|
11069
|
+
return window.Chart;
|
|
11070
|
+
}
|
|
11071
|
+
|
|
11072
|
+
/**
|
|
11073
|
+
* This file is largely inspired by owl 1.
|
|
11074
|
+
* `css` tag has been removed from owl 2 without workaround to manage css.
|
|
11075
|
+
* So, the solution was to import the behavior of owl 1 directly in our
|
|
11076
|
+
* codebase, with one difference: the css is added to the sheet as soon as the
|
|
11077
|
+
* css tag is executed. In owl 1, the css was added as soon as a Component was
|
|
11078
|
+
* created for the first time.
|
|
11079
|
+
*/
|
|
11080
|
+
const STYLESHEETS = {};
|
|
11081
|
+
let nextId = 0;
|
|
11082
|
+
/**
|
|
11083
|
+
* CSS tag helper for defining inline stylesheets. With this, one can simply define
|
|
11084
|
+
* an inline stylesheet with just the following code:
|
|
11085
|
+
* ```js
|
|
11086
|
+
* css`.component-a { color: red; }`;
|
|
11087
|
+
* ```
|
|
11088
|
+
*/
|
|
11089
|
+
function css(strings, ...args) {
|
|
11090
|
+
const name = `__sheet__${nextId++}`;
|
|
11091
|
+
const value = String.raw(strings, ...args);
|
|
11092
|
+
registerSheet(name, value);
|
|
11093
|
+
activateSheet(name);
|
|
11094
|
+
return name;
|
|
11095
|
+
}
|
|
11096
|
+
function processSheet(str) {
|
|
11097
|
+
const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
|
|
11098
|
+
const selectorStack = [];
|
|
11099
|
+
const parts = [];
|
|
11100
|
+
let rules = [];
|
|
11101
|
+
function generateSelector(stackIndex, parentSelector) {
|
|
11102
|
+
const parts = [];
|
|
11103
|
+
for (const selector of selectorStack[stackIndex]) {
|
|
11104
|
+
let part = (parentSelector && parentSelector + " " + selector) || selector;
|
|
11105
|
+
if (part.includes("&")) {
|
|
11106
|
+
part = selector.replace(/&/g, parentSelector || "");
|
|
11107
|
+
}
|
|
11108
|
+
if (stackIndex < selectorStack.length - 1) {
|
|
11109
|
+
part = generateSelector(stackIndex + 1, part);
|
|
11110
|
+
}
|
|
11111
|
+
parts.push(part);
|
|
11112
|
+
}
|
|
11113
|
+
return parts.join(", ");
|
|
11114
|
+
}
|
|
11115
|
+
function generateRules() {
|
|
11116
|
+
if (rules.length) {
|
|
11117
|
+
parts.push(generateSelector(0) + " {");
|
|
11118
|
+
parts.push(...rules);
|
|
11119
|
+
parts.push("}");
|
|
11120
|
+
rules = [];
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
while (tokens.length) {
|
|
11124
|
+
let token = tokens.shift();
|
|
11125
|
+
if (token === "}") {
|
|
11126
|
+
generateRules();
|
|
11127
|
+
selectorStack.pop();
|
|
11128
|
+
}
|
|
11129
|
+
else {
|
|
11130
|
+
if (tokens[0] === "{") {
|
|
11131
|
+
generateRules();
|
|
11132
|
+
selectorStack.push(token.split(/\s*,\s*/));
|
|
11133
|
+
tokens.shift();
|
|
11134
|
+
}
|
|
11135
|
+
if (tokens[0] === ";") {
|
|
11136
|
+
rules.push(" " + token + ";");
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
}
|
|
11140
|
+
return parts.join("\n");
|
|
11141
|
+
}
|
|
11142
|
+
function registerSheet(id, css) {
|
|
11143
|
+
const sheet = document.createElement("style");
|
|
11144
|
+
sheet.textContent = processSheet(css);
|
|
11145
|
+
STYLESHEETS[id] = sheet;
|
|
11146
|
+
}
|
|
11147
|
+
function activateSheet(id) {
|
|
11148
|
+
const sheet = STYLESHEETS[id];
|
|
11149
|
+
sheet.setAttribute("component", id);
|
|
11150
|
+
document.head.appendChild(sheet);
|
|
11151
|
+
}
|
|
11152
|
+
function getTextDecoration({ strikethrough, underline, }) {
|
|
11153
|
+
if (!strikethrough && !underline) {
|
|
11154
|
+
return "none";
|
|
11155
|
+
}
|
|
11156
|
+
return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`;
|
|
11157
|
+
}
|
|
11158
|
+
/**
|
|
11159
|
+
* Convert the cell style to CSS properties.
|
|
11160
|
+
*/
|
|
11161
|
+
function cellStyleToCss(style) {
|
|
11162
|
+
const attributes = cellTextStyleToCss(style);
|
|
11163
|
+
if (!style)
|
|
11164
|
+
return attributes;
|
|
11165
|
+
if (style.fillColor) {
|
|
11166
|
+
attributes["background"] = style.fillColor;
|
|
11167
|
+
}
|
|
11168
|
+
return attributes;
|
|
11169
|
+
}
|
|
11170
|
+
/**
|
|
11171
|
+
* Convert the cell text style to CSS properties.
|
|
11172
|
+
*/
|
|
11173
|
+
function cellTextStyleToCss(style) {
|
|
11174
|
+
const attributes = {};
|
|
11175
|
+
if (!style)
|
|
11176
|
+
return attributes;
|
|
11177
|
+
if (style.bold) {
|
|
11178
|
+
attributes["font-weight"] = "bold";
|
|
11179
|
+
}
|
|
11180
|
+
if (style.italic) {
|
|
11181
|
+
attributes["font-style"] = "italic";
|
|
11182
|
+
}
|
|
11183
|
+
if (style.strikethrough || style.underline) {
|
|
11184
|
+
let decoration = style.strikethrough ? "line-through" : "";
|
|
11185
|
+
decoration = style.underline ? decoration + " underline" : decoration;
|
|
11186
|
+
attributes["text-decoration"] = decoration;
|
|
11187
|
+
}
|
|
11188
|
+
if (style.textColor) {
|
|
11189
|
+
attributes["color"] = style.textColor;
|
|
11190
|
+
}
|
|
11191
|
+
return attributes;
|
|
11192
|
+
}
|
|
11193
|
+
/**
|
|
11194
|
+
* Transform CSS properties into a CSS string.
|
|
11195
|
+
*/
|
|
11196
|
+
function cssPropertiesToCss(attributes) {
|
|
11197
|
+
let styleStr = "";
|
|
11198
|
+
for (const attName in attributes) {
|
|
11199
|
+
if (!attributes[attName]) {
|
|
11200
|
+
continue;
|
|
11201
|
+
}
|
|
11202
|
+
styleStr += `${attName}:${attributes[attName]}; `;
|
|
11203
|
+
}
|
|
11204
|
+
return styleStr;
|
|
11205
|
+
}
|
|
11206
|
+
function getElementMargins(el) {
|
|
11207
|
+
const style = window.getComputedStyle(el);
|
|
11208
|
+
return {
|
|
11209
|
+
top: parseInt(style.marginTop, 10) || 0,
|
|
11210
|
+
bottom: parseInt(style.marginBottom, 10) || 0,
|
|
11211
|
+
left: parseInt(style.marginLeft, 10) || 0,
|
|
11212
|
+
right: parseInt(style.marginRight, 10) || 0,
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
|
|
11216
|
+
css /* scss */ `
|
|
11217
|
+
.o-spreadsheet {
|
|
11218
|
+
.o-chart-custom-tooltip {
|
|
11219
|
+
font-size: 12px;
|
|
11220
|
+
background-color: #fff;
|
|
11221
|
+
z-index: ${ComponentsImportance.FigureTooltip};
|
|
11222
|
+
}
|
|
11223
|
+
}
|
|
11224
|
+
`;
|
|
11225
|
+
class ChartJsComponent extends Component {
|
|
11226
|
+
static template = "o-spreadsheet-ChartJsComponent";
|
|
11227
|
+
static props = {
|
|
11228
|
+
figure: Object,
|
|
11229
|
+
};
|
|
11230
|
+
canvas = useRef("graphContainer");
|
|
11231
|
+
chart;
|
|
11232
|
+
currentRuntime;
|
|
11233
|
+
get background() {
|
|
11234
|
+
return this.chartRuntime.background;
|
|
11235
|
+
}
|
|
11236
|
+
get canvasStyle() {
|
|
11237
|
+
return `background-color: ${this.background}`;
|
|
11238
|
+
}
|
|
11239
|
+
get chartRuntime() {
|
|
11240
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
|
|
11241
|
+
if (!("chartJsConfig" in runtime)) {
|
|
11242
|
+
throw new Error("Unsupported chart runtime");
|
|
11243
|
+
}
|
|
11244
|
+
return runtime;
|
|
11245
|
+
}
|
|
11246
|
+
setup() {
|
|
11247
|
+
onMounted(() => {
|
|
11248
|
+
const runtime = this.chartRuntime;
|
|
11249
|
+
this.currentRuntime = runtime;
|
|
11250
|
+
// Note: chartJS modify the runtime in place, so it's important to give it a copy
|
|
11251
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11252
|
+
});
|
|
11253
|
+
onWillUnmount(() => this.chart?.destroy());
|
|
11254
|
+
useEffect(() => {
|
|
11255
|
+
const runtime = this.chartRuntime;
|
|
11256
|
+
if (runtime !== this.currentRuntime) {
|
|
11257
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
11258
|
+
this.chart?.destroy();
|
|
11259
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11260
|
+
}
|
|
11261
|
+
else {
|
|
11262
|
+
this.updateChartJs(deepCopy(runtime));
|
|
11263
|
+
}
|
|
11264
|
+
this.currentRuntime = runtime;
|
|
11265
|
+
}
|
|
11266
|
+
});
|
|
11267
|
+
}
|
|
11268
|
+
createChart(chartData) {
|
|
11269
|
+
const canvas = this.canvas.el;
|
|
11270
|
+
const ctx = canvas.getContext("2d");
|
|
11271
|
+
const Chart = getChartJSConstructor();
|
|
11272
|
+
this.chart = new Chart(ctx, chartData);
|
|
11273
|
+
}
|
|
11274
|
+
updateChartJs(chartRuntime) {
|
|
11275
|
+
const chartData = chartRuntime.chartJsConfig;
|
|
11276
|
+
if (chartData.data && chartData.data.datasets) {
|
|
11277
|
+
this.chart.data = chartData.data;
|
|
11278
|
+
if (chartData.options?.plugins?.title) {
|
|
11279
|
+
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11282
|
+
else {
|
|
11283
|
+
this.chart.data.datasets = [];
|
|
11284
|
+
}
|
|
11285
|
+
this.chart.config.options = chartData.options;
|
|
11286
|
+
this.chart.update();
|
|
11287
|
+
}
|
|
11288
|
+
}
|
|
11289
|
+
|
|
10846
11290
|
class ScorecardChart extends Component {
|
|
10847
11291
|
static template = "o-spreadsheet-ScorecardChart";
|
|
10848
11292
|
static props = {
|
|
@@ -18684,7 +19128,7 @@ const HLOOKUP = {
|
|
|
18684
19128
|
const _isSorted = toBoolean(isSorted.value);
|
|
18685
19129
|
const colIndex = _isSorted
|
|
18686
19130
|
? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
|
|
18687
|
-
: linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
|
|
19131
|
+
: linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
|
|
18688
19132
|
const col = _range[colIndex];
|
|
18689
19133
|
if (col === undefined) {
|
|
18690
19134
|
return valueNotAvailable(searchKey);
|
|
@@ -18839,7 +19283,7 @@ const MATCH = {
|
|
|
18839
19283
|
index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
|
|
18840
19284
|
break;
|
|
18841
19285
|
case 0:
|
|
18842
|
-
index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
|
|
19286
|
+
index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
|
|
18843
19287
|
break;
|
|
18844
19288
|
case -1:
|
|
18845
19289
|
index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
|
|
@@ -18907,7 +19351,7 @@ const VLOOKUP = {
|
|
|
18907
19351
|
const _isSorted = toBoolean(isSorted.value);
|
|
18908
19352
|
const rowIndex = _isSorted
|
|
18909
19353
|
? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
|
|
18910
|
-
: linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
|
|
19354
|
+
: linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
|
|
18911
19355
|
const value = _range[_index - 1][rowIndex];
|
|
18912
19356
|
if (value === undefined) {
|
|
18913
19357
|
return valueNotAvailable(searchKey);
|
|
@@ -18963,7 +19407,7 @@ const XLOOKUP = {
|
|
|
18963
19407
|
const reverseSearch = _searchMode === -1;
|
|
18964
19408
|
const index = _searchMode === 2 || _searchMode === -2
|
|
18965
19409
|
? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
|
|
18966
|
-
: linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
|
|
19410
|
+
: linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
|
|
18967
19411
|
if (index !== -1) {
|
|
18968
19412
|
return lookupDirection === "col"
|
|
18969
19413
|
? _returnRange.map((col) => [col[index]])
|
|
@@ -22295,7 +22739,7 @@ autofillRulesRegistry
|
|
|
22295
22739
|
condition: (cell) => !cell.isFormula &&
|
|
22296
22740
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
|
|
22297
22741
|
alphaNumericValueRegExp.test(cell.content),
|
|
22298
|
-
generateRule: (cell, cells) => {
|
|
22742
|
+
generateRule: (cell, cells, direction) => {
|
|
22299
22743
|
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
22300
22744
|
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
22301
22745
|
const numberPostfixLength = cell.content.length - prefix.length;
|
|
@@ -22303,7 +22747,10 @@ autofillRulesRegistry
|
|
|
22303
22747
|
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
22304
22748
|
.filter((cell) => prefix === (cell.value ?? "").toString().match(stringPrefixRegExp)[0])
|
|
22305
22749
|
.map((cell) => parseInt((cell.value ?? "").toString().match(numberPostfixRegExp)[0]));
|
|
22306
|
-
|
|
22750
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22751
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22752
|
+
increment = -increment;
|
|
22753
|
+
}
|
|
22307
22754
|
return {
|
|
22308
22755
|
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
22309
22756
|
prefix,
|
|
@@ -22366,10 +22813,13 @@ autofillRulesRegistry
|
|
|
22366
22813
|
.add("increment_number", {
|
|
22367
22814
|
condition: (cell) => !cell.isFormula &&
|
|
22368
22815
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22369
|
-
generateRule: (cell, cells) => {
|
|
22816
|
+
generateRule: (cell, cells, direction) => {
|
|
22370
22817
|
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22371
22818
|
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22372
|
-
|
|
22819
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22820
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22821
|
+
increment = -increment;
|
|
22822
|
+
}
|
|
22373
22823
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22374
22824
|
return {
|
|
22375
22825
|
type: "INCREMENT_MODIFIER",
|
|
@@ -22413,343 +22863,6 @@ function getDateIntervals(dates) {
|
|
|
22413
22863
|
|
|
22414
22864
|
const cellPopoverRegistry = new Registry();
|
|
22415
22865
|
|
|
22416
|
-
const GAUGE_PADDING_SIDE = 30;
|
|
22417
|
-
const GAUGE_PADDING_TOP = 10;
|
|
22418
|
-
const GAUGE_PADDING_BOTTOM = 20;
|
|
22419
|
-
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
22420
|
-
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
22421
|
-
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
22422
|
-
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
22423
|
-
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
22424
|
-
function drawGaugeChart(canvas, runtime) {
|
|
22425
|
-
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
22426
|
-
canvas.width = canvasBoundingRect.width;
|
|
22427
|
-
canvas.height = canvasBoundingRect.height;
|
|
22428
|
-
const ctx = canvas.getContext("2d");
|
|
22429
|
-
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
22430
|
-
drawBackground(ctx, config);
|
|
22431
|
-
drawGauge(ctx, config);
|
|
22432
|
-
drawInflectionValues(ctx, config);
|
|
22433
|
-
drawLabels(ctx, config);
|
|
22434
|
-
drawTitle(ctx, config);
|
|
22435
|
-
}
|
|
22436
|
-
function drawGauge(ctx, config) {
|
|
22437
|
-
ctx.save();
|
|
22438
|
-
const gauge = config.gauge;
|
|
22439
|
-
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
22440
|
-
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
22441
|
-
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
22442
|
-
if (arcRadius < 0) {
|
|
22443
|
-
return;
|
|
22444
|
-
}
|
|
22445
|
-
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
22446
|
-
// Gauge background
|
|
22447
|
-
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
22448
|
-
ctx.beginPath();
|
|
22449
|
-
ctx.lineWidth = gauge.arcWidth;
|
|
22450
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
22451
|
-
ctx.stroke();
|
|
22452
|
-
// Gauge value
|
|
22453
|
-
ctx.strokeStyle = gauge.color;
|
|
22454
|
-
ctx.beginPath();
|
|
22455
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
22456
|
-
ctx.stroke();
|
|
22457
|
-
ctx.restore();
|
|
22458
|
-
}
|
|
22459
|
-
function drawBackground(ctx, config) {
|
|
22460
|
-
ctx.save();
|
|
22461
|
-
ctx.fillStyle = config.backgroundColor;
|
|
22462
|
-
ctx.fillRect(0, 0, config.width, config.height);
|
|
22463
|
-
ctx.restore();
|
|
22464
|
-
}
|
|
22465
|
-
function drawLabels(ctx, config) {
|
|
22466
|
-
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
22467
|
-
ctx.save();
|
|
22468
|
-
ctx.textAlign = "center";
|
|
22469
|
-
ctx.fillStyle = label.color;
|
|
22470
|
-
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
22471
|
-
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
22472
|
-
ctx.restore();
|
|
22473
|
-
}
|
|
22474
|
-
}
|
|
22475
|
-
function drawInflectionValues(ctx, config) {
|
|
22476
|
-
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
22477
|
-
for (const inflectionValue of config.inflectionValues) {
|
|
22478
|
-
ctx.save();
|
|
22479
|
-
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
22480
|
-
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
22481
|
-
ctx.lineWidth = 2;
|
|
22482
|
-
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
22483
|
-
ctx.beginPath();
|
|
22484
|
-
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
22485
|
-
ctx.lineTo(0, -height - 3);
|
|
22486
|
-
ctx.stroke();
|
|
22487
|
-
ctx.textAlign = "center";
|
|
22488
|
-
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
22489
|
-
ctx.fillStyle = inflectionValue.color;
|
|
22490
|
-
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
22491
|
-
ctx.fillText(inflectionValue.label, 0, textY);
|
|
22492
|
-
ctx.restore();
|
|
22493
|
-
}
|
|
22494
|
-
}
|
|
22495
|
-
function drawTitle(ctx, config) {
|
|
22496
|
-
ctx.save();
|
|
22497
|
-
const title = config.title;
|
|
22498
|
-
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
22499
|
-
ctx.textBaseline = "middle";
|
|
22500
|
-
ctx.fillStyle = title.color;
|
|
22501
|
-
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
22502
|
-
ctx.restore();
|
|
22503
|
-
}
|
|
22504
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
22505
|
-
const maxValue = runtime.maxValue;
|
|
22506
|
-
const minValue = runtime.minValue;
|
|
22507
|
-
const gaugeValue = runtime.gaugeValue;
|
|
22508
|
-
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
22509
|
-
const gaugeArcWidth = gaugeRect.width / 6;
|
|
22510
|
-
const gaugePercentage = gaugeValue
|
|
22511
|
-
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
22512
|
-
: 0;
|
|
22513
|
-
const gaugeValuePosition = {
|
|
22514
|
-
x: boundingRect.width / 2,
|
|
22515
|
-
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
22516
|
-
};
|
|
22517
|
-
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
22518
|
-
// Scale down the font size if the gaugeRect is too small
|
|
22519
|
-
if (gaugeRect.height < 300) {
|
|
22520
|
-
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
22521
|
-
}
|
|
22522
|
-
// Scale down the font size if the text is too long
|
|
22523
|
-
const maxTextWidth = gaugeRect.width / 2;
|
|
22524
|
-
const gaugeLabel = gaugeValue?.label || "-";
|
|
22525
|
-
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
22526
|
-
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
22527
|
-
}
|
|
22528
|
-
const minLabelPosition = {
|
|
22529
|
-
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
22530
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22531
|
-
};
|
|
22532
|
-
const maxLabelPosition = {
|
|
22533
|
-
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
22534
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22535
|
-
};
|
|
22536
|
-
const textColor = chartMutedFontColor(runtime.background);
|
|
22537
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
22538
|
-
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
22539
|
-
if (runtime.title.text) {
|
|
22540
|
-
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
22541
|
-
}
|
|
22542
|
-
switch (runtime.title.align) {
|
|
22543
|
-
case "right":
|
|
22544
|
-
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
22545
|
-
break;
|
|
22546
|
-
case "center":
|
|
22547
|
-
x = (boundingRect.width - titleWidth) / 2;
|
|
22548
|
-
break;
|
|
22549
|
-
case "left":
|
|
22550
|
-
default:
|
|
22551
|
-
x = CHART_PADDING$1;
|
|
22552
|
-
break;
|
|
22553
|
-
}
|
|
22554
|
-
return {
|
|
22555
|
-
width: boundingRect.width,
|
|
22556
|
-
height: boundingRect.height,
|
|
22557
|
-
title: {
|
|
22558
|
-
label: runtime.title.text ?? "",
|
|
22559
|
-
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
22560
|
-
textPosition: {
|
|
22561
|
-
x,
|
|
22562
|
-
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
22563
|
-
},
|
|
22564
|
-
color: runtime.title.color ?? textColor,
|
|
22565
|
-
bold: runtime.title.bold,
|
|
22566
|
-
italic: runtime.title.italic,
|
|
22567
|
-
},
|
|
22568
|
-
backgroundColor: runtime.background,
|
|
22569
|
-
gauge: {
|
|
22570
|
-
rect: gaugeRect,
|
|
22571
|
-
arcWidth: gaugeArcWidth,
|
|
22572
|
-
percentage: clip(gaugePercentage, 0, 1),
|
|
22573
|
-
color: getGaugeColor(runtime),
|
|
22574
|
-
},
|
|
22575
|
-
inflectionValues,
|
|
22576
|
-
gaugeValue: {
|
|
22577
|
-
label: gaugeLabel,
|
|
22578
|
-
textPosition: gaugeValuePosition,
|
|
22579
|
-
fontSize: gaugeValueFontSize,
|
|
22580
|
-
color: textColor,
|
|
22581
|
-
},
|
|
22582
|
-
minLabel: {
|
|
22583
|
-
label: runtime.minValue.label,
|
|
22584
|
-
textPosition: minLabelPosition,
|
|
22585
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22586
|
-
color: textColor,
|
|
22587
|
-
},
|
|
22588
|
-
maxLabel: {
|
|
22589
|
-
label: runtime.maxValue.label,
|
|
22590
|
-
textPosition: maxLabelPosition,
|
|
22591
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22592
|
-
color: textColor,
|
|
22593
|
-
},
|
|
22594
|
-
};
|
|
22595
|
-
}
|
|
22596
|
-
/**
|
|
22597
|
-
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
22598
|
-
* space for the title and labels.
|
|
22599
|
-
*/
|
|
22600
|
-
function getGaugeRect(boundingRect, title) {
|
|
22601
|
-
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
22602
|
-
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
22603
|
-
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
22604
|
-
let gaugeWidth;
|
|
22605
|
-
let gaugeHeight;
|
|
22606
|
-
if (drawWidth > 2 * drawHeight) {
|
|
22607
|
-
gaugeWidth = 2 * drawHeight;
|
|
22608
|
-
gaugeHeight = drawHeight;
|
|
22609
|
-
}
|
|
22610
|
-
else {
|
|
22611
|
-
gaugeWidth = drawWidth;
|
|
22612
|
-
gaugeHeight = drawWidth / 2;
|
|
22613
|
-
}
|
|
22614
|
-
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
22615
|
-
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
22616
|
-
return {
|
|
22617
|
-
x: gaugeX,
|
|
22618
|
-
y: gaugeY,
|
|
22619
|
-
width: gaugeWidth,
|
|
22620
|
-
height: gaugeHeight,
|
|
22621
|
-
};
|
|
22622
|
-
}
|
|
22623
|
-
/**
|
|
22624
|
-
* Get the infliction values of the gauge, and where to draw them (the angle from the center of the gauge at which they are drawn).
|
|
22625
|
-
*
|
|
22626
|
-
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
22627
|
-
*/
|
|
22628
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
22629
|
-
const maxValue = runtime.maxValue;
|
|
22630
|
-
const minValue = runtime.minValue;
|
|
22631
|
-
const gaugeCircleCenter = {
|
|
22632
|
-
x: gaugeRect.x + gaugeRect.width / 2,
|
|
22633
|
-
y: gaugeRect.y + gaugeRect.height,
|
|
22634
|
-
};
|
|
22635
|
-
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
22636
|
-
const inflectionValues = [];
|
|
22637
|
-
const inflectionValuesTextRects = [];
|
|
22638
|
-
for (const inflectionValue of runtime.inflectionValues) {
|
|
22639
|
-
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
22640
|
-
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
22641
|
-
const angle = Math.PI - Math.PI * percentage;
|
|
22642
|
-
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
22643
|
-
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
22644
|
-
gaugeCircleCenter.x, // center of the gauge circle
|
|
22645
|
-
gaugeCircleCenter.y, // center of the gauge circle
|
|
22646
|
-
labelWidth + 2, // width of the text + some margin
|
|
22647
|
-
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
22648
|
-
);
|
|
22649
|
-
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
22650
|
-
? GAUGE_LABELS_FONT_SIZE
|
|
22651
|
-
: 0;
|
|
22652
|
-
inflectionValuesTextRects.push(textRect);
|
|
22653
|
-
inflectionValues.push({
|
|
22654
|
-
rotation: angle,
|
|
22655
|
-
label: inflectionValue.label,
|
|
22656
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22657
|
-
color: textColor,
|
|
22658
|
-
offset,
|
|
22659
|
-
});
|
|
22660
|
-
}
|
|
22661
|
-
return inflectionValues;
|
|
22662
|
-
}
|
|
22663
|
-
function getGaugeColor(runtime) {
|
|
22664
|
-
const gaugeValue = runtime.gaugeValue?.value;
|
|
22665
|
-
if (gaugeValue === undefined) {
|
|
22666
|
-
return GAUGE_BACKGROUND_COLOR;
|
|
22667
|
-
}
|
|
22668
|
-
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
22669
|
-
const inflectionValue = runtime.inflectionValues[i];
|
|
22670
|
-
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
22671
|
-
return runtime.colors[i];
|
|
22672
|
-
}
|
|
22673
|
-
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
22674
|
-
return runtime.colors[i];
|
|
22675
|
-
}
|
|
22676
|
-
}
|
|
22677
|
-
return runtime.colors.at(-1);
|
|
22678
|
-
}
|
|
22679
|
-
function getSegmentsOfRectangle(rectangle) {
|
|
22680
|
-
return [
|
|
22681
|
-
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
22682
|
-
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
22683
|
-
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
22684
|
-
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
22685
|
-
];
|
|
22686
|
-
}
|
|
22687
|
-
/**
|
|
22688
|
-
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
22689
|
-
* is not handled.
|
|
22690
|
-
*/
|
|
22691
|
-
function doSegmentIntersect(segment1, segment2) {
|
|
22692
|
-
const A = segment1.start;
|
|
22693
|
-
const B = segment1.end;
|
|
22694
|
-
const C = segment2.start;
|
|
22695
|
-
const D = segment2.end;
|
|
22696
|
-
/**
|
|
22697
|
-
* Line segment intersection algorithm
|
|
22698
|
-
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
22699
|
-
*/
|
|
22700
|
-
function ccw(a, b, c) {
|
|
22701
|
-
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
22702
|
-
}
|
|
22703
|
-
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
22704
|
-
}
|
|
22705
|
-
function doRectanglesIntersect(rect1, rect2) {
|
|
22706
|
-
const segments1 = getSegmentsOfRectangle(rect1);
|
|
22707
|
-
const segments2 = getSegmentsOfRectangle(rect2);
|
|
22708
|
-
for (const segment1 of segments1) {
|
|
22709
|
-
for (const segment2 of segments2) {
|
|
22710
|
-
if (doSegmentIntersect(segment1, segment2)) {
|
|
22711
|
-
return true;
|
|
22712
|
-
}
|
|
22713
|
-
}
|
|
22714
|
-
}
|
|
22715
|
-
return false;
|
|
22716
|
-
}
|
|
22717
|
-
/**
|
|
22718
|
-
* Get the rectangle that is tangent to a circle at a given angle.
|
|
22719
|
-
*
|
|
22720
|
-
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
22721
|
-
*/
|
|
22722
|
-
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
22723
|
-
const cos = Math.cos(angle);
|
|
22724
|
-
const sin = Math.sin(angle);
|
|
22725
|
-
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
22726
|
-
const x = cos * radius;
|
|
22727
|
-
const y = sin * radius;
|
|
22728
|
-
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
22729
|
-
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
22730
|
-
const y2 = cos * (rectWidth / 2);
|
|
22731
|
-
const bottomRight = {
|
|
22732
|
-
x: x + x2 + circleCenterX,
|
|
22733
|
-
y: circleCenterY - (y - y2),
|
|
22734
|
-
};
|
|
22735
|
-
const bottomLeft = {
|
|
22736
|
-
x: x - x2 + circleCenterX,
|
|
22737
|
-
y: circleCenterY - (y + y2),
|
|
22738
|
-
};
|
|
22739
|
-
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
22740
|
-
const xp = cos * (radius + rectHeight);
|
|
22741
|
-
const yp = sin * (radius + rectHeight);
|
|
22742
|
-
const topLeft = {
|
|
22743
|
-
x: xp - x2 + circleCenterX,
|
|
22744
|
-
y: circleCenterY - (yp + y2),
|
|
22745
|
-
};
|
|
22746
|
-
const topRight = {
|
|
22747
|
-
x: xp + x2 + circleCenterX,
|
|
22748
|
-
y: circleCenterY - (yp - y2),
|
|
22749
|
-
};
|
|
22750
|
-
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
22751
|
-
}
|
|
22752
|
-
|
|
22753
22866
|
class GaugeChartComponent extends Component {
|
|
22754
22867
|
static template = "o-spreadsheet-GaugeChartComponent";
|
|
22755
22868
|
canvas = useRef("chartContainer");
|
|
@@ -22782,72 +22895,6 @@ function toXlsxHexColor(color) {
|
|
|
22782
22895
|
return color;
|
|
22783
22896
|
}
|
|
22784
22897
|
|
|
22785
|
-
const CHART_COMMON_OPTIONS = {
|
|
22786
|
-
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
22787
|
-
responsive: true, // will resize when its container is resized
|
|
22788
|
-
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
22789
|
-
elements: {
|
|
22790
|
-
line: {
|
|
22791
|
-
fill: false, // do not fill the area under line charts
|
|
22792
|
-
},
|
|
22793
|
-
point: {
|
|
22794
|
-
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
22795
|
-
},
|
|
22796
|
-
},
|
|
22797
|
-
animation: false,
|
|
22798
|
-
};
|
|
22799
|
-
function chartToImage(runtime, figure, type) {
|
|
22800
|
-
// wrap the canvas in a div with a fixed size because chart.js would
|
|
22801
|
-
// fill the whole page otherwise
|
|
22802
|
-
const div = document.createElement("div");
|
|
22803
|
-
div.style.width = `${figure.width}px`;
|
|
22804
|
-
div.style.height = `${figure.height}px`;
|
|
22805
|
-
const canvas = document.createElement("canvas");
|
|
22806
|
-
div.append(canvas);
|
|
22807
|
-
canvas.setAttribute("width", figure.width.toString());
|
|
22808
|
-
canvas.setAttribute("height", figure.height.toString());
|
|
22809
|
-
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
22810
|
-
document.body.append(div);
|
|
22811
|
-
if ("chartJsConfig" in runtime) {
|
|
22812
|
-
const config = deepCopy(runtime.chartJsConfig);
|
|
22813
|
-
config.plugins = [backgroundColorChartJSPlugin];
|
|
22814
|
-
const chart = new window.Chart(canvas, config);
|
|
22815
|
-
const imgContent = chart.toBase64Image();
|
|
22816
|
-
chart.destroy();
|
|
22817
|
-
div.remove();
|
|
22818
|
-
return imgContent;
|
|
22819
|
-
}
|
|
22820
|
-
else if (type === "scorecard") {
|
|
22821
|
-
const design = getScorecardConfiguration(figure, runtime);
|
|
22822
|
-
drawScoreChart(design, canvas);
|
|
22823
|
-
const imgContent = canvas.toDataURL();
|
|
22824
|
-
div.remove();
|
|
22825
|
-
return imgContent;
|
|
22826
|
-
}
|
|
22827
|
-
else if (type === "gauge") {
|
|
22828
|
-
drawGaugeChart(canvas, runtime);
|
|
22829
|
-
const imgContent = canvas.toDataURL();
|
|
22830
|
-
div.remove();
|
|
22831
|
-
return imgContent;
|
|
22832
|
-
}
|
|
22833
|
-
return undefined;
|
|
22834
|
-
}
|
|
22835
|
-
/**
|
|
22836
|
-
* Custom chart.js plugin to set the background color of the canvas
|
|
22837
|
-
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
22838
|
-
*/
|
|
22839
|
-
const backgroundColorChartJSPlugin = {
|
|
22840
|
-
id: "customCanvasBackgroundColor",
|
|
22841
|
-
beforeDraw: (chart) => {
|
|
22842
|
-
const { ctx } = chart;
|
|
22843
|
-
ctx.save();
|
|
22844
|
-
ctx.globalCompositeOperation = "destination-over";
|
|
22845
|
-
ctx.fillStyle = "#ffffff";
|
|
22846
|
-
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
22847
|
-
ctx.restore();
|
|
22848
|
-
},
|
|
22849
|
-
};
|
|
22850
|
-
|
|
22851
22898
|
/**
|
|
22852
22899
|
* Represent a raw XML string
|
|
22853
22900
|
*/
|
|
@@ -22913,6 +22960,7 @@ const CONTENT_TYPES = {
|
|
|
22913
22960
|
macroEnabledTemplateWorkbook: "application/vnd.ms-excel.template.macroEnabled.main+xml",
|
|
22914
22961
|
excelAddInWorkbook: "application/vnd.ms-excel.addin.macroEnabled.main+xml",
|
|
22915
22962
|
sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
|
22963
|
+
metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
|
|
22916
22964
|
sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
|
22917
22965
|
styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
|
22918
22966
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
@@ -22925,6 +22973,7 @@ const CONTENT_TYPES = {
|
|
|
22925
22973
|
const XLSX_RELATION_TYPE = {
|
|
22926
22974
|
document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
22927
22975
|
sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
|
22976
|
+
metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
|
|
22928
22977
|
sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
|
|
22929
22978
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
22930
22979
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -22934,6 +22983,7 @@ const XLSX_RELATION_TYPE = {
|
|
|
22934
22983
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22935
22984
|
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22936
22985
|
};
|
|
22986
|
+
const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
|
|
22937
22987
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22938
22988
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
22939
22989
|
/**
|
|
@@ -25355,29 +25405,34 @@ function convertPivotTableConfig(pivotTable) {
|
|
|
25355
25405
|
* In all the sheets, replace the table-only references in the formula cells with standard references.
|
|
25356
25406
|
*/
|
|
25357
25407
|
function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
25358
|
-
for (let
|
|
25359
|
-
const tables = xlsxSheets.find((s) => s.sheetName ===
|
|
25408
|
+
for (let tableSheet of convertedSheets) {
|
|
25409
|
+
const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
|
|
25360
25410
|
for (let table of tables) {
|
|
25361
25411
|
const tabRef = table.name + "[";
|
|
25362
|
-
for (let
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25412
|
+
for (let sheet of convertedSheets) {
|
|
25413
|
+
for (let xc in sheet.cells) {
|
|
25414
|
+
const cell = sheet.cells[xc];
|
|
25415
|
+
let cellContent = sheet.cells[xc];
|
|
25416
|
+
if (cell && cellContent && cellContent.startsWith("=")) {
|
|
25417
|
+
let refIndex;
|
|
25418
|
+
while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
|
|
25419
|
+
let endIndex = refIndex + tabRef.length;
|
|
25420
|
+
let openBrackets = 1;
|
|
25421
|
+
while (openBrackets > 0 && endIndex < cellContent.length) {
|
|
25422
|
+
if (cellContent[endIndex] === "[") {
|
|
25423
|
+
openBrackets++;
|
|
25424
|
+
}
|
|
25425
|
+
else if (cellContent[endIndex] === "]") {
|
|
25426
|
+
openBrackets--;
|
|
25427
|
+
}
|
|
25428
|
+
endIndex++;
|
|
25429
|
+
}
|
|
25430
|
+
let reference = cellContent.slice(refIndex + tabRef.length, endIndex - 1);
|
|
25431
|
+
const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
|
|
25432
|
+
const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
|
|
25433
|
+
cellContent =
|
|
25434
|
+
cellContent.slice(0, refIndex) + convertedRef + cellContent.slice(endIndex);
|
|
25374
25435
|
}
|
|
25375
|
-
reference = reference.slice(0, endIndex);
|
|
25376
|
-
const convertedRef = convertTableReference(reference, table, xc);
|
|
25377
|
-
cellContent =
|
|
25378
|
-
cellContent.slice(0, refIndex) +
|
|
25379
|
-
convertedRef +
|
|
25380
|
-
cellContent.slice(tabRef.length + refIndex + endIndex + 1);
|
|
25381
25436
|
}
|
|
25382
25437
|
sheet.cells[xc] = cellContent;
|
|
25383
25438
|
}
|
|
@@ -25386,11 +25441,17 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25386
25441
|
}
|
|
25387
25442
|
}
|
|
25388
25443
|
/**
|
|
25389
|
-
* Convert table-specific references in formulas into standard references.
|
|
25444
|
+
* Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
|
|
25445
|
+
* and of keywords determining the rows of the table to reference.
|
|
25390
25446
|
*
|
|
25391
25447
|
* A reference in a table can have the form (only the part between brackets should be given to this function):
|
|
25392
25448
|
* - tableName[colName] : reference to the whole column "colName"
|
|
25449
|
+
* - tableName[#keyword] : reference to the whatever row the keyword refers to
|
|
25393
25450
|
* - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
|
|
25451
|
+
* - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
|
|
25452
|
+
* - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
|
|
25453
|
+
* - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
|
|
25454
|
+
*
|
|
25394
25455
|
*
|
|
25395
25456
|
* The available keywords are :
|
|
25396
25457
|
* - #All : all the column (including totals)
|
|
@@ -25398,58 +25459,109 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25398
25459
|
* - #Headers : only the header of the column
|
|
25399
25460
|
* - #Totals : only the totals of the column
|
|
25400
25461
|
* - #This Row : only the element in the same row as the cell
|
|
25462
|
+
*
|
|
25463
|
+
* Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
|
|
25401
25464
|
*/
|
|
25402
|
-
function convertTableReference(expr, table, cellXc) {
|
|
25403
|
-
|
|
25465
|
+
function convertTableReference(sheetPrefix, expr, table, cellXc) {
|
|
25466
|
+
// TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
|
|
25467
|
+
// contain # or , characters. But that's probably an edge case that we can ignore for now.
|
|
25468
|
+
const parts = expr.split(",").map((part) => part.trim());
|
|
25404
25469
|
const tableZone = toZone(table.ref);
|
|
25405
|
-
const
|
|
25406
|
-
|
|
25407
|
-
|
|
25408
|
-
|
|
25409
|
-
|
|
25410
|
-
|
|
25411
|
-
|
|
25412
|
-
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25470
|
+
const colIndexes = [];
|
|
25471
|
+
const rowIndexes = [];
|
|
25472
|
+
const foundKeywords = [];
|
|
25473
|
+
for (const part of parts) {
|
|
25474
|
+
if (removeBrackets(part).startsWith("#")) {
|
|
25475
|
+
const keyWord = removeBrackets(part);
|
|
25476
|
+
foundKeywords.push(keyWord);
|
|
25477
|
+
switch (keyWord) {
|
|
25478
|
+
case "#All":
|
|
25479
|
+
rowIndexes.push(tableZone.top, tableZone.bottom);
|
|
25480
|
+
break;
|
|
25481
|
+
case "#Data":
|
|
25482
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25483
|
+
const bottom = table.totalsRowCount
|
|
25484
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25485
|
+
: tableZone.bottom;
|
|
25486
|
+
rowIndexes.push(top, bottom);
|
|
25487
|
+
break;
|
|
25488
|
+
case "#This Row":
|
|
25489
|
+
rowIndexes.push(toCartesian(cellXc).row);
|
|
25490
|
+
break;
|
|
25491
|
+
case "#Headers":
|
|
25492
|
+
if (!table.headerRowCount) {
|
|
25493
|
+
return CellErrorType.InvalidReference;
|
|
25494
|
+
}
|
|
25495
|
+
rowIndexes.push(tableZone.top);
|
|
25496
|
+
break;
|
|
25497
|
+
case "#Totals":
|
|
25498
|
+
if (!table.totalsRowCount) {
|
|
25499
|
+
return CellErrorType.InvalidReference;
|
|
25500
|
+
}
|
|
25501
|
+
rowIndexes.push(tableZone.bottom);
|
|
25502
|
+
break;
|
|
25503
|
+
}
|
|
25416
25504
|
}
|
|
25417
|
-
|
|
25418
|
-
|
|
25419
|
-
|
|
25420
|
-
|
|
25421
|
-
|
|
25422
|
-
|
|
25423
|
-
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
|
|
25428
|
-
|
|
25429
|
-
|
|
25430
|
-
|
|
25431
|
-
|
|
25432
|
-
|
|
25433
|
-
|
|
25434
|
-
if (!table.headerRowCount) {
|
|
25435
|
-
isReferencedZoneValid = false;
|
|
25436
|
-
}
|
|
25437
|
-
break;
|
|
25438
|
-
case "#Totals":
|
|
25439
|
-
refZone.top = refZone.bottom = tableZone.bottom;
|
|
25440
|
-
if (!table.totalsRowCount) {
|
|
25441
|
-
isReferencedZoneValid = false;
|
|
25505
|
+
else {
|
|
25506
|
+
const columns = part
|
|
25507
|
+
.split(":")
|
|
25508
|
+
.map((part) => part.trim())
|
|
25509
|
+
.map(removeBrackets);
|
|
25510
|
+
if (colIndexes.length) {
|
|
25511
|
+
return CellErrorType.InvalidReference;
|
|
25512
|
+
}
|
|
25513
|
+
const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
|
|
25514
|
+
if (colRelativeIndex === -1) {
|
|
25515
|
+
return CellErrorType.InvalidReference;
|
|
25516
|
+
}
|
|
25517
|
+
colIndexes.push(colRelativeIndex + tableZone.left);
|
|
25518
|
+
if (columns[1]) {
|
|
25519
|
+
const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
|
|
25520
|
+
if (colRelativeIndex2 === -1) {
|
|
25521
|
+
return CellErrorType.InvalidReference;
|
|
25442
25522
|
}
|
|
25443
|
-
|
|
25523
|
+
colIndexes.push(colRelativeIndex2 + tableZone.left);
|
|
25524
|
+
}
|
|
25444
25525
|
}
|
|
25445
|
-
const colRef = refElements[1].slice(1, refElements[1].length - 1);
|
|
25446
|
-
const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
|
|
25447
|
-
refZone.left = refZone.right = colRelativeIndex + tableZone.left;
|
|
25448
25526
|
}
|
|
25449
|
-
if (!
|
|
25527
|
+
if (!areKeywordsCompatible(foundKeywords)) {
|
|
25450
25528
|
return CellErrorType.InvalidReference;
|
|
25451
25529
|
}
|
|
25452
|
-
|
|
25530
|
+
if (rowIndexes.length === 0) {
|
|
25531
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25532
|
+
const bottom = table.totalsRowCount
|
|
25533
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25534
|
+
: tableZone.bottom;
|
|
25535
|
+
rowIndexes.push(top, bottom);
|
|
25536
|
+
}
|
|
25537
|
+
if (colIndexes.length === 0) {
|
|
25538
|
+
colIndexes.push(tableZone.left, tableZone.right);
|
|
25539
|
+
}
|
|
25540
|
+
const refZone = {
|
|
25541
|
+
top: Math.min(...rowIndexes),
|
|
25542
|
+
left: Math.min(...colIndexes),
|
|
25543
|
+
bottom: Math.max(...rowIndexes),
|
|
25544
|
+
right: Math.max(...colIndexes),
|
|
25545
|
+
};
|
|
25546
|
+
return sheetPrefix + zoneToXc(refZone);
|
|
25547
|
+
}
|
|
25548
|
+
function removeBrackets(str) {
|
|
25549
|
+
return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
|
|
25550
|
+
}
|
|
25551
|
+
function areKeywordsCompatible(keywords) {
|
|
25552
|
+
if (keywords.length < 2) {
|
|
25553
|
+
return true;
|
|
25554
|
+
}
|
|
25555
|
+
else if (keywords.length > 2) {
|
|
25556
|
+
return false;
|
|
25557
|
+
}
|
|
25558
|
+
else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
|
|
25559
|
+
return true;
|
|
25560
|
+
}
|
|
25561
|
+
else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
|
|
25562
|
+
return true;
|
|
25563
|
+
}
|
|
25564
|
+
return false;
|
|
25453
25565
|
}
|
|
25454
25566
|
|
|
25455
25567
|
// -------------------------------------
|
|
@@ -28302,7 +28414,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
|
|
|
28302
28414
|
}
|
|
28303
28415
|
function getPyramidChartData(definition, dataSets, labelRange, getters) {
|
|
28304
28416
|
const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
|
|
28305
|
-
const barDataset = barChartData.dataSetsValues;
|
|
28417
|
+
const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
|
|
28306
28418
|
const pyramidDatasetValues = [];
|
|
28307
28419
|
if (barDataset[0]) {
|
|
28308
28420
|
const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
|
|
@@ -28617,11 +28729,12 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
|
|
|
28617
28729
|
}
|
|
28618
28730
|
let missingTimeAdapterAlreadyWarned = false;
|
|
28619
28731
|
function isLuxonTimeAdapterInstalled() {
|
|
28620
|
-
|
|
28732
|
+
const Chart = getChartJSConstructor();
|
|
28733
|
+
if (!Chart) {
|
|
28621
28734
|
return false;
|
|
28622
28735
|
}
|
|
28623
28736
|
// @ts-ignore
|
|
28624
|
-
const adapter = new
|
|
28737
|
+
const adapter = new Chart._adapters._date({});
|
|
28625
28738
|
const isInstalled = adapter._id === "luxon";
|
|
28626
28739
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
28627
28740
|
missingTimeAdapterAlreadyWarned = true;
|
|
@@ -28779,10 +28892,8 @@ function getChartDatasetFormat(getters, allDataSets, axis) {
|
|
|
28779
28892
|
function getChartDatasetValues(getters, dataSets) {
|
|
28780
28893
|
const datasetValues = [];
|
|
28781
28894
|
for (const [dsIndex, ds] of Object.entries(dataSets)) {
|
|
28782
|
-
if (getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left)) {
|
|
28783
|
-
continue;
|
|
28784
|
-
}
|
|
28785
28895
|
let label;
|
|
28896
|
+
let hidden = getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left);
|
|
28786
28897
|
if (ds.labelCell) {
|
|
28787
28898
|
const labelRange = ds.labelCell;
|
|
28788
28899
|
const cell = labelRange
|
|
@@ -28809,9 +28920,9 @@ function getChartDatasetValues(getters, dataSets) {
|
|
|
28809
28920
|
data.fill(1);
|
|
28810
28921
|
}
|
|
28811
28922
|
else if (data.every((cell) => cell === undefined || cell === null || !isNumber(cell.toString(), DEFAULT_LOCALE))) {
|
|
28812
|
-
|
|
28923
|
+
hidden = true;
|
|
28813
28924
|
}
|
|
28814
|
-
datasetValues.push({ data, label });
|
|
28925
|
+
datasetValues.push({ data, label, hidden });
|
|
28815
28926
|
}
|
|
28816
28927
|
return datasetValues;
|
|
28817
28928
|
}
|
|
@@ -28822,12 +28933,13 @@ function getBarChartDatasets(definition, args) {
|
|
|
28822
28933
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28823
28934
|
const trendDatasets = [];
|
|
28824
28935
|
for (const index in dataSetsValues) {
|
|
28825
|
-
let { label, data } = dataSetsValues[index];
|
|
28936
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28826
28937
|
label = definition.dataSets?.[index].label || label;
|
|
28827
28938
|
const backgroundColor = colors.next();
|
|
28828
28939
|
const dataset = {
|
|
28829
28940
|
label,
|
|
28830
28941
|
data,
|
|
28942
|
+
hidden,
|
|
28831
28943
|
borderColor: definition.background || BACKGROUND_CHART_COLOR,
|
|
28832
28944
|
borderWidth: definition.stacked ? 1 : 0,
|
|
28833
28945
|
backgroundColor,
|
|
@@ -28860,6 +28972,9 @@ function getWaterfallDatasetAndLabels(definition, args) {
|
|
|
28860
28972
|
const labelsWithSubTotals = [];
|
|
28861
28973
|
let lastValue = 0;
|
|
28862
28974
|
for (const dataSetsValue of dataSetsValues) {
|
|
28975
|
+
if (dataSetsValue.hidden) {
|
|
28976
|
+
continue;
|
|
28977
|
+
}
|
|
28863
28978
|
for (let i = 0; i < dataSetsValue.data.length; i++) {
|
|
28864
28979
|
const data = dataSetsValue.data[i];
|
|
28865
28980
|
labelsWithSubTotals.push(labels[i]);
|
|
@@ -28895,7 +29010,7 @@ function getLineChartDatasets(definition, args) {
|
|
|
28895
29010
|
const trendDatasets = [];
|
|
28896
29011
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28897
29012
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
28898
|
-
let { label, data } = dataSetsValues[index];
|
|
29013
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28899
29014
|
label = definition.dataSets?.[index].label || label;
|
|
28900
29015
|
const color = colors.next();
|
|
28901
29016
|
if (axisType && ["linear", "time"].includes(axisType)) {
|
|
@@ -28905,6 +29020,7 @@ function getLineChartDatasets(definition, args) {
|
|
|
28905
29020
|
const dataset = {
|
|
28906
29021
|
label,
|
|
28907
29022
|
data,
|
|
29023
|
+
hidden,
|
|
28908
29024
|
tension: 0, // 0 -> render straight lines, which is much faster
|
|
28909
29025
|
borderColor: color,
|
|
28910
29026
|
backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
|
|
@@ -28937,7 +29053,9 @@ function getPieChartDatasets(definition, args) {
|
|
|
28937
29053
|
const dataSets = [];
|
|
28938
29054
|
const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
|
|
28939
29055
|
const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
|
|
28940
|
-
for (const { label, data } of dataSetsValues) {
|
|
29056
|
+
for (const { label, data, hidden } of dataSetsValues) {
|
|
29057
|
+
if (hidden)
|
|
29058
|
+
continue;
|
|
28941
29059
|
const dataset = {
|
|
28942
29060
|
label,
|
|
28943
29061
|
data,
|
|
@@ -28955,7 +29073,7 @@ function getComboChartDatasets(definition, args) {
|
|
|
28955
29073
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28956
29074
|
const trendDatasets = [];
|
|
28957
29075
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
28958
|
-
let { label, data } = dataSetsValues[index];
|
|
29076
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28959
29077
|
label = definition.dataSets?.[index].label || label;
|
|
28960
29078
|
const design = definition.dataSets?.[index];
|
|
28961
29079
|
const color = colors.next();
|
|
@@ -28963,6 +29081,7 @@ function getComboChartDatasets(definition, args) {
|
|
|
28963
29081
|
const dataset = {
|
|
28964
29082
|
label: label,
|
|
28965
29083
|
data,
|
|
29084
|
+
hidden,
|
|
28966
29085
|
borderColor: color,
|
|
28967
29086
|
backgroundColor: color,
|
|
28968
29087
|
yAxisID: definition.dataSets?.[index].yAxisId || "y",
|
|
@@ -28987,7 +29106,7 @@ function getRadarChartDatasets(definition, args) {
|
|
|
28987
29106
|
const fill = definition.fillArea ?? false;
|
|
28988
29107
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28989
29108
|
for (let i = 0; i < dataSetsValues.length; i++) {
|
|
28990
|
-
let { label, data } = dataSetsValues[i];
|
|
29109
|
+
let { label, data, hidden } = dataSetsValues[i];
|
|
28991
29110
|
if (definition.dataSets?.[i]?.label) {
|
|
28992
29111
|
label = definition.dataSets[i].label;
|
|
28993
29112
|
}
|
|
@@ -28995,6 +29114,7 @@ function getRadarChartDatasets(definition, args) {
|
|
|
28995
29114
|
const dataset = {
|
|
28996
29115
|
label,
|
|
28997
29116
|
data,
|
|
29117
|
+
hidden,
|
|
28998
29118
|
borderColor,
|
|
28999
29119
|
backgroundColor: borderColor,
|
|
29000
29120
|
};
|
|
@@ -29140,6 +29260,11 @@ function getPieChartLegend(definition, args) {
|
|
|
29140
29260
|
hidden: false,
|
|
29141
29261
|
lineWidth: 2,
|
|
29142
29262
|
})),
|
|
29263
|
+
filter: (legendItem, data) => {
|
|
29264
|
+
return "datasetIndex" in legendItem
|
|
29265
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29266
|
+
: true;
|
|
29267
|
+
},
|
|
29143
29268
|
},
|
|
29144
29269
|
};
|
|
29145
29270
|
}
|
|
@@ -29201,6 +29326,11 @@ function getWaterfallChartLegend(definition, args) {
|
|
|
29201
29326
|
}
|
|
29202
29327
|
return legendValues;
|
|
29203
29328
|
},
|
|
29329
|
+
filter: (legendItem, data) => {
|
|
29330
|
+
return "datasetIndex" in legendItem
|
|
29331
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29332
|
+
: true;
|
|
29333
|
+
},
|
|
29204
29334
|
},
|
|
29205
29335
|
onClick: () => { }, // Disables click interaction with the waterfall chart legend items
|
|
29206
29336
|
};
|
|
@@ -29284,6 +29414,11 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
|
|
|
29284
29414
|
...legendLabelConfig,
|
|
29285
29415
|
};
|
|
29286
29416
|
}),
|
|
29417
|
+
filter: (legendItem, data) => {
|
|
29418
|
+
return "datasetIndex" in legendItem
|
|
29419
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29420
|
+
: true;
|
|
29421
|
+
},
|
|
29287
29422
|
},
|
|
29288
29423
|
};
|
|
29289
29424
|
}
|
|
@@ -29617,7 +29752,7 @@ const templates = /* xml */ `
|
|
|
29617
29752
|
<div
|
|
29618
29753
|
class="o-chart-custom-tooltip border rounded px-2 py-1 pe-none mw-100 position-absolute text-nowrap shadow opacity-100">
|
|
29619
29754
|
<table class="overflow-hidden m-0">
|
|
29620
|
-
<thead>
|
|
29755
|
+
<thead t-if="title">
|
|
29621
29756
|
<tr>
|
|
29622
29757
|
<th class="o-tooltip-title align-baseline border-0 text-truncate" t-esc="title" t-attf-style="max-width: {{ labelsMaxWidth }}"/>
|
|
29623
29758
|
</tr>
|
|
@@ -29678,8 +29813,8 @@ function getBarChartTooltip(definition, args) {
|
|
|
29678
29813
|
? undefined
|
|
29679
29814
|
: "";
|
|
29680
29815
|
},
|
|
29816
|
+
beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
|
|
29681
29817
|
label: function (tooltipItem) {
|
|
29682
|
-
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
29683
29818
|
const horizontalChart = definition.horizontal;
|
|
29684
29819
|
let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
|
|
29685
29820
|
if (yLabel === undefined || yLabel === null) {
|
|
@@ -29687,7 +29822,7 @@ function getBarChartTooltip(definition, args) {
|
|
|
29687
29822
|
}
|
|
29688
29823
|
const axisId = horizontalChart ? tooltipItem.dataset.xAxisID : tooltipItem.dataset.yAxisID;
|
|
29689
29824
|
const yLabelStr = formatChartDatasetValue(args.axisFormats, args.locale)(yLabel, axisId);
|
|
29690
|
-
return
|
|
29825
|
+
return yLabelStr;
|
|
29691
29826
|
},
|
|
29692
29827
|
},
|
|
29693
29828
|
};
|
|
@@ -29712,21 +29847,18 @@ function getLineChartTooltip(definition, args) {
|
|
|
29712
29847
|
const formattedX = formatValue(label, { locale, format: labelFormat });
|
|
29713
29848
|
const axisId = tooltipItem.dataset.yAxisID || "y";
|
|
29714
29849
|
const formattedY = formatValue(dataSetPoint, { locale, format: axisFormats?.[axisId] });
|
|
29715
|
-
|
|
29716
|
-
return formattedX
|
|
29717
|
-
? `${dataSetTitle}: (${formattedX}, ${formattedY})`
|
|
29718
|
-
: `${dataSetTitle}: ${formattedY}`;
|
|
29850
|
+
return formattedX ? `(${formattedX}, ${formattedY})` : `${formattedY}`;
|
|
29719
29851
|
};
|
|
29720
29852
|
}
|
|
29721
29853
|
else {
|
|
29722
29854
|
tooltip.callbacks.label = function (tooltipItem) {
|
|
29723
|
-
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
29724
29855
|
const yLabel = tooltipItem.parsed.y;
|
|
29725
29856
|
const axisId = tooltipItem.dataset.yAxisID;
|
|
29726
29857
|
const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
|
|
29727
|
-
return
|
|
29858
|
+
return yLabelStr;
|
|
29728
29859
|
};
|
|
29729
29860
|
}
|
|
29861
|
+
tooltip.callbacks.beforeLabel = (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label;
|
|
29730
29862
|
tooltip.callbacks.title = function (tooltipItems) {
|
|
29731
29863
|
const displayTooltipTitle = axisType !== "linear" &&
|
|
29732
29864
|
tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
|
|
@@ -29744,17 +29876,15 @@ function getPieChartTooltip(definition, args) {
|
|
|
29744
29876
|
title: function (tooltipItems) {
|
|
29745
29877
|
return tooltipItems[0].dataset.label;
|
|
29746
29878
|
},
|
|
29879
|
+
beforeLabel: (tooltipItem) => tooltipItem.label || tooltipItem.dataset.label,
|
|
29747
29880
|
label: function (tooltipItem) {
|
|
29748
29881
|
const data = tooltipItem.dataset.data;
|
|
29749
29882
|
const dataIndex = tooltipItem.dataIndex;
|
|
29750
29883
|
const percentage = calculatePercentage(data, dataIndex);
|
|
29751
|
-
const xLabel = tooltipItem.label || tooltipItem.dataset.label;
|
|
29752
29884
|
const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
|
|
29753
29885
|
const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
|
|
29754
29886
|
const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
|
|
29755
|
-
return
|
|
29756
|
-
? `${xLabel}: ${yLabelStr} (${percentage}%)`
|
|
29757
|
-
: `${yLabelStr} (${percentage}%)`;
|
|
29887
|
+
return `${yLabelStr} (${percentage}%)`;
|
|
29758
29888
|
},
|
|
29759
29889
|
},
|
|
29760
29890
|
};
|
|
@@ -29767,16 +29897,17 @@ function getWaterfallChartTooltip(definition, args) {
|
|
|
29767
29897
|
enabled: false,
|
|
29768
29898
|
external: customTooltipHandler,
|
|
29769
29899
|
callbacks: {
|
|
29770
|
-
|
|
29771
|
-
const [lastValue, currentValue] = tooltipItem.raw;
|
|
29772
|
-
const yLabel = currentValue - lastValue;
|
|
29900
|
+
beforeLabel: function (tooltipItem) {
|
|
29773
29901
|
const dataSeriesIndex = labels.length
|
|
29774
29902
|
? Math.floor(tooltipItem.dataIndex / labels.length)
|
|
29775
29903
|
: 0;
|
|
29776
|
-
|
|
29904
|
+
return dataSeriesLabels[dataSeriesIndex];
|
|
29905
|
+
},
|
|
29906
|
+
label: function (tooltipItem) {
|
|
29907
|
+
const [lastValue, currentValue] = tooltipItem.raw;
|
|
29908
|
+
const yLabel = currentValue - lastValue;
|
|
29777
29909
|
const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
|
|
29778
|
-
|
|
29779
|
-
return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
|
|
29910
|
+
return formatValue(yLabel, { format: toolTipFormat, locale });
|
|
29780
29911
|
},
|
|
29781
29912
|
},
|
|
29782
29913
|
};
|
|
@@ -29800,11 +29931,10 @@ function getRadarChartTooltip(definition, args) {
|
|
|
29800
29931
|
enabled: false,
|
|
29801
29932
|
external: customTooltipHandler,
|
|
29802
29933
|
callbacks: {
|
|
29934
|
+
beforeLabel: (tooltipItem) => tooltipItem.dataset?.label || tooltipItem.label,
|
|
29803
29935
|
label: function (tooltipItem) {
|
|
29804
|
-
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
29805
29936
|
const yLabel = tooltipItem.parsed.r;
|
|
29806
|
-
|
|
29807
|
-
return xLabel ? `${xLabel}: ${formattedY}` : formattedY;
|
|
29937
|
+
return formatValue(yLabel, { format: axisFormats?.r, locale });
|
|
29808
29938
|
},
|
|
29809
29939
|
},
|
|
29810
29940
|
};
|
|
@@ -29819,13 +29949,12 @@ function getGeoChartTooltip(definition, args) {
|
|
|
29819
29949
|
return tooltipItem.raw.value !== undefined;
|
|
29820
29950
|
},
|
|
29821
29951
|
callbacks: {
|
|
29952
|
+
beforeLabel: (tooltipItem) => tooltipItem.raw.feature.properties.name,
|
|
29822
29953
|
label: function (tooltipItem) {
|
|
29823
29954
|
const rawItem = tooltipItem.raw;
|
|
29824
|
-
const xLabel = rawItem.feature.properties.name;
|
|
29825
29955
|
const yLabel = rawItem.value;
|
|
29826
29956
|
const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
|
|
29827
|
-
|
|
29828
|
-
return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
|
|
29957
|
+
return formatValue(yLabel, { format: toolTipFormat, locale });
|
|
29829
29958
|
},
|
|
29830
29959
|
},
|
|
29831
29960
|
};
|
|
@@ -29845,7 +29974,8 @@ function customTooltipHandler({ chart, tooltip }) {
|
|
|
29845
29974
|
return;
|
|
29846
29975
|
}
|
|
29847
29976
|
const tooltipItems = tooltip.body.map((body, index) => {
|
|
29848
|
-
let
|
|
29977
|
+
let label = body.before[0];
|
|
29978
|
+
let value = body.lines[0];
|
|
29849
29979
|
if (!value) {
|
|
29850
29980
|
value = label;
|
|
29851
29981
|
label = "";
|
|
@@ -32458,10 +32588,6 @@ class Popover extends Component {
|
|
|
32458
32588
|
this.currentDisplayValue = newDisplay;
|
|
32459
32589
|
if (!anchor)
|
|
32460
32590
|
return;
|
|
32461
|
-
el.style.top = "";
|
|
32462
|
-
el.style.left = "";
|
|
32463
|
-
el.style["max-height"] = "";
|
|
32464
|
-
el.style["max-width"] = "";
|
|
32465
32591
|
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
32466
32592
|
let elDims = {
|
|
32467
32593
|
width: el.getBoundingClientRect().width,
|
|
@@ -34013,6 +34139,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
|
|
|
34013
34139
|
duplicateLabelRangeInDuplicatedSheet: duplicateLabelRangeInDuplicatedSheet,
|
|
34014
34140
|
formatChartDatasetValue: formatChartDatasetValue,
|
|
34015
34141
|
formatTickValue: formatTickValue,
|
|
34142
|
+
getChartJSConstructor: getChartJSConstructor,
|
|
34016
34143
|
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
34017
34144
|
getDefinedAxis: getDefinedAxis,
|
|
34018
34145
|
getPieColors: getPieColors,
|
|
@@ -37813,6 +37940,11 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
37813
37940
|
}
|
|
37814
37941
|
updateColors(colors) {
|
|
37815
37942
|
this.colors = colors;
|
|
37943
|
+
const colorGenerator = new ColorGenerator(this.ranges.length, this.colors);
|
|
37944
|
+
this.ranges = this.ranges.map((range) => ({
|
|
37945
|
+
...range,
|
|
37946
|
+
color: colorGenerator.next(),
|
|
37947
|
+
}));
|
|
37816
37948
|
}
|
|
37817
37949
|
confirm() {
|
|
37818
37950
|
for (const range of this.selectionInputs) {
|
|
@@ -37847,12 +37979,11 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
37847
37979
|
* e.g. ["A1", "Sheet2!B3", "E12"]
|
|
37848
37980
|
*/
|
|
37849
37981
|
get selectionInputs() {
|
|
37850
|
-
const generator = new ColorGenerator(this.ranges.length, this.colors);
|
|
37851
37982
|
return this.ranges.map((input, index) => Object.assign({}, input, {
|
|
37852
37983
|
color: this.hasMainFocus &&
|
|
37853
37984
|
this.focusedRangeIndex !== null &&
|
|
37854
37985
|
this.getters.isRangeValid(input.xc)
|
|
37855
|
-
?
|
|
37986
|
+
? input.color
|
|
37856
37987
|
: null,
|
|
37857
37988
|
isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
|
|
37858
37989
|
isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
|
|
@@ -38122,10 +38253,10 @@ class SelectionInput extends Component {
|
|
|
38122
38253
|
if (originalIndex === finalIndex) {
|
|
38123
38254
|
return;
|
|
38124
38255
|
}
|
|
38125
|
-
const
|
|
38126
|
-
|
|
38127
|
-
|
|
38128
|
-
this.props.onSelectionReordered?.(
|
|
38256
|
+
const indexes = range(0, draggableIds.length);
|
|
38257
|
+
indexes.splice(originalIndex, 1);
|
|
38258
|
+
indexes.splice(finalIndex, 0, originalIndex);
|
|
38259
|
+
this.props.onSelectionReordered?.(indexes);
|
|
38129
38260
|
this.props.onSelectionConfirmed?.();
|
|
38130
38261
|
this.store.confirm();
|
|
38131
38262
|
},
|
|
@@ -38403,6 +38534,9 @@ class GenericChartConfigPanel extends Component {
|
|
|
38403
38534
|
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
|
|
38404
38535
|
dataSets: this.dataSets,
|
|
38405
38536
|
});
|
|
38537
|
+
if (this.state.datasetDispatchResult.isSuccessful) {
|
|
38538
|
+
this.dataSets = this.env.model.getters.getChartDefinition(this.props.figureId).dataSets;
|
|
38539
|
+
}
|
|
38406
38540
|
}
|
|
38407
38541
|
getDataSeriesRanges() {
|
|
38408
38542
|
return this.dataSets;
|
|
@@ -39819,8 +39953,16 @@ class ContentEditableHelper {
|
|
|
39819
39953
|
}
|
|
39820
39954
|
let startNode = this.findChildAtCharacterIndex(start);
|
|
39821
39955
|
let endNode = this.findChildAtCharacterIndex(end);
|
|
39822
|
-
|
|
39823
|
-
|
|
39956
|
+
// setEnd (setStart) will result in a collapsed range if the end point is before the start point
|
|
39957
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd
|
|
39958
|
+
if (start <= end) {
|
|
39959
|
+
range.setStart(startNode.node, startNode.offset);
|
|
39960
|
+
range.setEnd(endNode.node, endNode.offset);
|
|
39961
|
+
}
|
|
39962
|
+
else {
|
|
39963
|
+
range.setStart(endNode.node, endNode.offset);
|
|
39964
|
+
range.setEnd(startNode.node, startNode.offset);
|
|
39965
|
+
}
|
|
39824
39966
|
}
|
|
39825
39967
|
}
|
|
39826
39968
|
/**
|
|
@@ -40122,8 +40264,7 @@ css /* scss */ `
|
|
|
40122
40264
|
}
|
|
40123
40265
|
|
|
40124
40266
|
.o-composer-assistant {
|
|
40125
|
-
|
|
40126
|
-
margin: 1px 4px;
|
|
40267
|
+
margin-top: 1px;
|
|
40127
40268
|
|
|
40128
40269
|
.o-semi-bold {
|
|
40129
40270
|
/* FIXME: to remove in favor of Bootstrap
|
|
@@ -40174,10 +40315,11 @@ class Composer extends Component {
|
|
|
40174
40315
|
});
|
|
40175
40316
|
compositionActive = false;
|
|
40176
40317
|
spreadsheetRect = useSpreadsheetRect();
|
|
40177
|
-
get
|
|
40318
|
+
get assistantStyleProperties() {
|
|
40178
40319
|
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
40179
40320
|
const assistantStyle = {};
|
|
40180
|
-
|
|
40321
|
+
const minWidth = Math.min(this.props.rect?.width || Infinity, ASSISTANT_WIDTH);
|
|
40322
|
+
assistantStyle["min-width"] = `${minWidth}px`;
|
|
40181
40323
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
40182
40324
|
const proposalsHaveDescription = proposals?.some((proposal) => proposal.description);
|
|
40183
40325
|
if (this.functionDescriptionState.showDescription || proposalsHaveDescription) {
|
|
@@ -40201,13 +40343,29 @@ class Composer extends Component {
|
|
|
40201
40343
|
}
|
|
40202
40344
|
}
|
|
40203
40345
|
else {
|
|
40204
|
-
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
40346
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom - 1}px`; // -1: margin
|
|
40205
40347
|
if (composerRect.left + ASSISTANT_WIDTH + SCROLLBAR_WIDTH + CLOSE_ICON_RADIUS >
|
|
40206
40348
|
this.spreadsheetRect.width) {
|
|
40207
40349
|
assistantStyle.right = `${CLOSE_ICON_RADIUS}px`;
|
|
40208
40350
|
}
|
|
40209
40351
|
}
|
|
40210
|
-
return
|
|
40352
|
+
return assistantStyle;
|
|
40353
|
+
}
|
|
40354
|
+
get assistantStyle() {
|
|
40355
|
+
const allProperties = this.assistantStyleProperties;
|
|
40356
|
+
return cssPropertiesToCss({
|
|
40357
|
+
"max-height": allProperties["max-height"],
|
|
40358
|
+
width: allProperties["width"],
|
|
40359
|
+
"min-width": allProperties["min-width"],
|
|
40360
|
+
});
|
|
40361
|
+
}
|
|
40362
|
+
get assistantContainerStyle() {
|
|
40363
|
+
const allProperties = this.assistantStyleProperties;
|
|
40364
|
+
return cssPropertiesToCss({
|
|
40365
|
+
top: allProperties["top"],
|
|
40366
|
+
right: allProperties["right"],
|
|
40367
|
+
transform: allProperties["transform"],
|
|
40368
|
+
});
|
|
40211
40369
|
}
|
|
40212
40370
|
// we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
|
|
40213
40371
|
shouldProcessInputEvents = false;
|
|
@@ -46681,9 +46839,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46681
46839
|
pivot: this.draft,
|
|
46682
46840
|
});
|
|
46683
46841
|
this.draft = null;
|
|
46684
|
-
if (!this.alreadyNotified &&
|
|
46685
|
-
!this.isDynamicPivotInViewport() &&
|
|
46686
|
-
this.isStaticPivotInViewport()) {
|
|
46842
|
+
if (!this.alreadyNotified && this.isUpdatedPivotVisibleInViewportOnlyAsStaticPivot()) {
|
|
46687
46843
|
const formulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46688
46844
|
const pivotExample = `=PIVOT(${formulaId})`;
|
|
46689
46845
|
this.alreadyNotified = true;
|
|
@@ -46739,26 +46895,33 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46739
46895
|
this.applyUpdate();
|
|
46740
46896
|
}
|
|
46741
46897
|
}
|
|
46742
|
-
|
|
46743
|
-
|
|
46744
|
-
|
|
46745
|
-
|
|
46746
|
-
|
|
46747
|
-
|
|
46748
|
-
|
|
46749
|
-
return false;
|
|
46750
|
-
}
|
|
46751
|
-
isStaticPivotInViewport() {
|
|
46898
|
+
/**
|
|
46899
|
+
* @returns true if the updated pivot is visible in the viewport only as a
|
|
46900
|
+
* static pivot and not as a dynamic pivot
|
|
46901
|
+
*/
|
|
46902
|
+
isUpdatedPivotVisibleInViewportOnlyAsStaticPivot() {
|
|
46903
|
+
let staticPivotCount = 0;
|
|
46904
|
+
const updatedPivotFormulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46752
46905
|
for (const position of this.getters.getVisibleCellPositions()) {
|
|
46753
46906
|
const cell = this.getters.getCell(position);
|
|
46754
46907
|
if (cell?.isFormula) {
|
|
46755
46908
|
const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
46756
|
-
|
|
46757
|
-
|
|
46909
|
+
const pivotFormulaId = pivotFunction?.args[0]?.value;
|
|
46910
|
+
if (pivotFunction && updatedPivotFormulaId === pivotFormulaId.toString()) {
|
|
46911
|
+
if (pivotFunction.functionName === "PIVOT") {
|
|
46912
|
+
// if we have at least one dynamic pivot visible inserted the viewport
|
|
46913
|
+
// we return false
|
|
46914
|
+
return false;
|
|
46915
|
+
}
|
|
46916
|
+
else {
|
|
46917
|
+
staticPivotCount++;
|
|
46918
|
+
}
|
|
46758
46919
|
}
|
|
46759
46920
|
}
|
|
46760
46921
|
}
|
|
46761
|
-
return
|
|
46922
|
+
// we return true if there are only static pivots visible inserted the viewport,
|
|
46923
|
+
// otherwise false
|
|
46924
|
+
return staticPivotCount > 0;
|
|
46762
46925
|
}
|
|
46763
46926
|
addDefaultDateTimeGranularity(fields, definition) {
|
|
46764
46927
|
const { columns, rows } = definition;
|
|
@@ -51637,8 +51800,8 @@ class Border extends Component {
|
|
|
51637
51800
|
css /* scss */ `
|
|
51638
51801
|
.o-corner {
|
|
51639
51802
|
position: absolute;
|
|
51640
|
-
height:
|
|
51641
|
-
width:
|
|
51803
|
+
height: 8px;
|
|
51804
|
+
width: 8px;
|
|
51642
51805
|
border: 1px solid white;
|
|
51643
51806
|
}
|
|
51644
51807
|
.o-corner-nw,
|
|
@@ -59999,6 +60162,10 @@ class Evaluator {
|
|
|
59999
60162
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
60000
60163
|
this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
|
|
60001
60164
|
this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
|
|
60165
|
+
this.compilationParams.evalContext.lookupCaches = {
|
|
60166
|
+
forwardSearch: new Map(),
|
|
60167
|
+
reverseSearch: new Map(),
|
|
60168
|
+
};
|
|
60002
60169
|
}
|
|
60003
60170
|
createEmptyPositionSet() {
|
|
60004
60171
|
const sheetSizes = {};
|
|
@@ -60608,6 +60775,7 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60608
60775
|
exportForExcel(data) {
|
|
60609
60776
|
for (const sheet of data.sheets) {
|
|
60610
60777
|
sheet.cellValues = {};
|
|
60778
|
+
sheet.formulaSpillRanges = {};
|
|
60611
60779
|
}
|
|
60612
60780
|
for (const position of this.evaluator.getEvaluatedPositions()) {
|
|
60613
60781
|
const evaluatedCell = this.evaluator.getEvaluatedCell(position);
|
|
@@ -60619,8 +60787,9 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60619
60787
|
const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
|
|
60620
60788
|
const formulaCell = this.getCorrespondingFormulaCell(position);
|
|
60621
60789
|
if (formulaCell) {
|
|
60790
|
+
const cell = this.getters.getCell(position);
|
|
60622
60791
|
isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
|
|
60623
|
-
isFormula = isExported;
|
|
60792
|
+
isFormula = isExported && cell?.content === formulaCell.content;
|
|
60624
60793
|
// If the cell contains a non-exported formula and that is evaluates to
|
|
60625
60794
|
// nothing* ,we don't export it.
|
|
60626
60795
|
// * non-falsy value are relevant and so are 0 and FALSE, which only leaves
|
|
@@ -60643,7 +60812,11 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60643
60812
|
content = !isExported ? newContent : exportedCellData;
|
|
60644
60813
|
}
|
|
60645
60814
|
exportedSheetData.cells[xc] = content;
|
|
60646
|
-
exportedSheetData.cellValues[xc] = value;
|
|
60815
|
+
exportedSheetData.cellValues[xc] = evaluatedCell.type !== "error" ? value : undefined;
|
|
60816
|
+
const spillZone = this.getSpreadZone(position);
|
|
60817
|
+
if (spillZone) {
|
|
60818
|
+
exportedSheetData.formulaSpillRanges[xc] = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
|
|
60819
|
+
}
|
|
60647
60820
|
}
|
|
60648
60821
|
}
|
|
60649
60822
|
/**
|
|
@@ -62871,7 +63044,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
62871
63044
|
getRule(cell, cells) {
|
|
62872
63045
|
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
62873
63046
|
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
62874
|
-
return rule && rule.generateRule(cell, cells);
|
|
63047
|
+
return rule && this.direction && rule.generateRule(cell, cells, this.direction);
|
|
62875
63048
|
}
|
|
62876
63049
|
/**
|
|
62877
63050
|
* Create the generator to be able to autofill the next cells.
|
|
@@ -73353,7 +73526,7 @@ function numberRef(reference) {
|
|
|
73353
73526
|
`;
|
|
73354
73527
|
}
|
|
73355
73528
|
|
|
73356
|
-
function addFormula(formula, value) {
|
|
73529
|
+
function addFormula(formula, value, formulaSpillRange) {
|
|
73357
73530
|
if (!formula) {
|
|
73358
73531
|
return { attrs: [], node: escapeXml `` };
|
|
73359
73532
|
}
|
|
@@ -73361,10 +73534,17 @@ function addFormula(formula, value) {
|
|
|
73361
73534
|
if (type === undefined) {
|
|
73362
73535
|
return { attrs: [], node: escapeXml `` };
|
|
73363
73536
|
}
|
|
73364
|
-
const attrs = [
|
|
73537
|
+
const attrs = [
|
|
73538
|
+
["cm", "1"],
|
|
73539
|
+
["t", type],
|
|
73540
|
+
];
|
|
73365
73541
|
const XlsxFormula = adaptFormulaToExcel(formula);
|
|
73366
73542
|
const exportedValue = adaptFormulaValueToExcel(value);
|
|
73367
|
-
|
|
73543
|
+
// We treat all formulas as array formulas (a simple formula
|
|
73544
|
+
// is an array formula that spills on only one cell) to avoid
|
|
73545
|
+
// trying to detect spilling sub-formulas which is not a trivial task.
|
|
73546
|
+
let node;
|
|
73547
|
+
node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
|
|
73368
73548
|
return { attrs, node };
|
|
73369
73549
|
}
|
|
73370
73550
|
function addContent(content, sharedStrings, forceString = false) {
|
|
@@ -74354,7 +74534,7 @@ function addRows(construct, data, sheet) {
|
|
|
74354
74534
|
let cellNode = escapeXml ``;
|
|
74355
74535
|
// Either formula or static value inside the cell
|
|
74356
74536
|
if (content?.startsWith("=") && value !== undefined) {
|
|
74357
|
-
const res = addFormula(content, value);
|
|
74537
|
+
const res = addFormula(content, value, sheet.formulaSpillRanges[xc] ?? xc);
|
|
74358
74538
|
if (!res) {
|
|
74359
74539
|
continue;
|
|
74360
74540
|
}
|
|
@@ -74640,6 +74820,30 @@ function createWorksheets(data, construct) {
|
|
|
74640
74820
|
`;
|
|
74641
74821
|
files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
|
|
74642
74822
|
}
|
|
74823
|
+
const sheetMetadataXml = escapeXml /*xml*/ `
|
|
74824
|
+
<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
|
|
74825
|
+
<metadataTypes count="1">
|
|
74826
|
+
<metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
|
|
74827
|
+
pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
|
|
74828
|
+
clearComments="1" assign="1" coerce="1" cellMeta="1" />
|
|
74829
|
+
</metadataTypes>
|
|
74830
|
+
<futureMetadata name="XLDAPR" count="1">
|
|
74831
|
+
<bk>
|
|
74832
|
+
<extLst>
|
|
74833
|
+
<ext uri="{${ARRAY_FORMULA_URI}}">
|
|
74834
|
+
<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
|
|
74835
|
+
</ext>
|
|
74836
|
+
</extLst>
|
|
74837
|
+
</bk>
|
|
74838
|
+
</futureMetadata>
|
|
74839
|
+
<cellMetadata count="1">
|
|
74840
|
+
<bk>
|
|
74841
|
+
<rc t="1" v="0" />
|
|
74842
|
+
</bk>
|
|
74843
|
+
</cellMetadata>
|
|
74844
|
+
</metadata>
|
|
74845
|
+
`;
|
|
74846
|
+
files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
|
|
74643
74847
|
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74644
74848
|
type: XLSX_RELATION_TYPE.sharedStrings,
|
|
74645
74849
|
target: "sharedStrings.xml",
|
|
@@ -74648,6 +74852,10 @@ function createWorksheets(data, construct) {
|
|
|
74648
74852
|
type: XLSX_RELATION_TYPE.styles,
|
|
74649
74853
|
target: "styles.xml",
|
|
74650
74854
|
});
|
|
74855
|
+
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74856
|
+
type: XLSX_RELATION_TYPE.metadata,
|
|
74857
|
+
target: "metadata.xml",
|
|
74858
|
+
});
|
|
74651
74859
|
return files;
|
|
74652
74860
|
}
|
|
74653
74861
|
/**
|
|
@@ -75564,6 +75772,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
|
|
|
75564
75772
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, 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 };
|
|
75565
75773
|
|
|
75566
75774
|
|
|
75567
|
-
__info__.version = "18.2.
|
|
75568
|
-
__info__.date = "2025-
|
|
75569
|
-
__info__.hash = "
|
|
75775
|
+
__info__.version = "18.2.2";
|
|
75776
|
+
__info__.date = "2025-03-07T10:41:04.411Z";
|
|
75777
|
+
__info__.hash = "f567932";
|