@odoo/o-spreadsheet 18.1.9 → 18.1.11
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 +842 -621
- package/dist/o-spreadsheet.d.ts +16 -4
- package/dist/o-spreadsheet.esm.js +842 -621
- package/dist/o-spreadsheet.iife.js +842 -621
- package/dist/o-spreadsheet.iife.min.js +401 -380
- package/dist/o_spreadsheet.xml +55 -37
- package/package.json +1 -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.1.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.1.11
|
|
6
|
+
* @date 2025-03-12T15:31:44.276Z
|
|
7
|
+
* @hash 7de2363
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
|
|
@@ -422,7 +422,6 @@ function escapeRegExp(str) {
|
|
|
422
422
|
* Sparse arrays remain sparse.
|
|
423
423
|
*/
|
|
424
424
|
function deepCopy(obj) {
|
|
425
|
-
const result = Array.isArray(obj) ? [] : {};
|
|
426
425
|
switch (typeof obj) {
|
|
427
426
|
case "object": {
|
|
428
427
|
if (obj === null) {
|
|
@@ -434,8 +433,18 @@ function deepCopy(obj) {
|
|
|
434
433
|
else if (!(isPlainObject(obj) || obj instanceof Array)) {
|
|
435
434
|
throw new Error("Unsupported type: only objects and arrays are supported");
|
|
436
435
|
}
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
const result = Array.isArray(obj) ? new Array(obj.length) : {};
|
|
437
|
+
if (Array.isArray(obj)) {
|
|
438
|
+
for (let i = 0, len = obj.length; i < len; i++) {
|
|
439
|
+
if (i in obj) {
|
|
440
|
+
result[i] = deepCopy(obj[i]);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
for (const key in obj) {
|
|
446
|
+
result[key] = deepCopy(obj[key]);
|
|
447
|
+
}
|
|
439
448
|
}
|
|
440
449
|
return result;
|
|
441
450
|
}
|
|
@@ -2688,21 +2697,30 @@ function mergeContiguousZones(zones) {
|
|
|
2688
2697
|
return mergedZones;
|
|
2689
2698
|
}
|
|
2690
2699
|
|
|
2700
|
+
const globalReverseLookup$1 = new WeakMap();
|
|
2701
|
+
const globalIdCounter = new WeakMap();
|
|
2691
2702
|
/**
|
|
2692
2703
|
* Get the id of the given item (its key in the given dictionary).
|
|
2693
2704
|
* If the given item does not exist in the dictionary, it creates one with a new id.
|
|
2694
2705
|
*/
|
|
2695
2706
|
function getItemId(item, itemsDic) {
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2707
|
+
if (!globalReverseLookup$1.has(itemsDic)) {
|
|
2708
|
+
globalReverseLookup$1.set(itemsDic, new Map());
|
|
2709
|
+
globalIdCounter.set(itemsDic, 0);
|
|
2710
|
+
}
|
|
2711
|
+
const reverseLookup = globalReverseLookup$1.get(itemsDic);
|
|
2712
|
+
const canonical = getCanonicalRepresentation(item);
|
|
2713
|
+
if (reverseLookup.has(canonical)) {
|
|
2714
|
+
const id = reverseLookup.get(canonical);
|
|
2715
|
+
itemsDic[id] = item;
|
|
2716
|
+
return id;
|
|
2700
2717
|
}
|
|
2701
2718
|
// Generate new Id if the item didn't exist in the dictionary
|
|
2702
|
-
const
|
|
2703
|
-
|
|
2704
|
-
itemsDic
|
|
2705
|
-
|
|
2719
|
+
const newId = globalIdCounter.get(itemsDic) + 1;
|
|
2720
|
+
reverseLookup.set(canonical, newId);
|
|
2721
|
+
globalIdCounter.set(itemsDic, newId);
|
|
2722
|
+
itemsDic[newId] = item;
|
|
2723
|
+
return newId;
|
|
2706
2724
|
}
|
|
2707
2725
|
function groupItemIdsByZones(positionsByItemId) {
|
|
2708
2726
|
const result = {};
|
|
@@ -2726,6 +2744,33 @@ function* iterateItemIdsPositions(sheetId, itemIdsByZones) {
|
|
|
2726
2744
|
}
|
|
2727
2745
|
}
|
|
2728
2746
|
}
|
|
2747
|
+
function getCanonicalRepresentation(item) {
|
|
2748
|
+
if (item === null)
|
|
2749
|
+
return "null";
|
|
2750
|
+
if (item === undefined)
|
|
2751
|
+
return "undefined";
|
|
2752
|
+
if (typeof item !== "object")
|
|
2753
|
+
return String(item);
|
|
2754
|
+
if (Array.isArray(item)) {
|
|
2755
|
+
const len = item.length;
|
|
2756
|
+
let result = "[";
|
|
2757
|
+
for (let i = 0; i < len; i++) {
|
|
2758
|
+
if (i > 0)
|
|
2759
|
+
result += ",";
|
|
2760
|
+
result += getCanonicalRepresentation(item[i]);
|
|
2761
|
+
}
|
|
2762
|
+
return result + "]";
|
|
2763
|
+
}
|
|
2764
|
+
const keys = Object.keys(item).sort();
|
|
2765
|
+
let repr = "{";
|
|
2766
|
+
for (const key of keys) {
|
|
2767
|
+
if (item[key] !== undefined) {
|
|
2768
|
+
repr += `"${key}":${getCanonicalRepresentation(item[key])},`;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
repr += "}";
|
|
2772
|
+
return repr;
|
|
2773
|
+
}
|
|
2729
2774
|
|
|
2730
2775
|
// -----------------------------------------------------------------------------
|
|
2731
2776
|
// Date Type
|
|
@@ -6091,8 +6136,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6091
6136
|
if (zone.bottom !== zone.top && zone.left != zone.right) {
|
|
6092
6137
|
if (zone.right) {
|
|
6093
6138
|
for (let j = zone.left; j <= zone.right; ++j) {
|
|
6139
|
+
const datasetOptions = j === zone.left ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6094
6140
|
postProcessedRanges.push({
|
|
6095
|
-
...
|
|
6141
|
+
...datasetOptions,
|
|
6096
6142
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6097
6143
|
left: j,
|
|
6098
6144
|
right: j,
|
|
@@ -6104,8 +6150,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6104
6150
|
}
|
|
6105
6151
|
else {
|
|
6106
6152
|
for (let j = zone.top; j <= zone.bottom; ++j) {
|
|
6153
|
+
const datasetOptions = j === zone.top ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6107
6154
|
postProcessedRanges.push({
|
|
6108
|
-
...
|
|
6155
|
+
...datasetOptions,
|
|
6109
6156
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6110
6157
|
left: zone.left,
|
|
6111
6158
|
right: zone.right,
|
|
@@ -8277,7 +8324,8 @@ function isSortedColumnValid(sortedColumn, pivot) {
|
|
|
8277
8324
|
const possibleValues = pivot
|
|
8278
8325
|
.getPossibleFieldValues(columns[i])
|
|
8279
8326
|
.map((v) => v.value);
|
|
8280
|
-
if (!possibleValues.includes(sortedColumn.domain[i].value)
|
|
8327
|
+
if (!possibleValues.includes(sortedColumn.domain[i].value) &&
|
|
8328
|
+
!(sortedColumn.domain[i].value === null && possibleValues.includes(""))) {
|
|
8281
8329
|
return false;
|
|
8282
8330
|
}
|
|
8283
8331
|
}
|
|
@@ -10056,70 +10104,341 @@ function getNextNonEmptyBar(bars, startIndex) {
|
|
|
10056
10104
|
return bars.find((bar, i) => i > startIndex && bar.height !== 0);
|
|
10057
10105
|
}
|
|
10058
10106
|
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10107
|
+
const GAUGE_PADDING_SIDE = 30;
|
|
10108
|
+
const GAUGE_PADDING_TOP = 10;
|
|
10109
|
+
const GAUGE_PADDING_BOTTOM = 20;
|
|
10110
|
+
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
10111
|
+
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
10112
|
+
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
10113
|
+
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
10114
|
+
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
10115
|
+
function drawGaugeChart(canvas, runtime) {
|
|
10116
|
+
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
10117
|
+
canvas.width = canvasBoundingRect.width;
|
|
10118
|
+
canvas.height = canvasBoundingRect.height;
|
|
10119
|
+
const ctx = canvas.getContext("2d");
|
|
10120
|
+
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
10121
|
+
drawBackground(ctx, config);
|
|
10122
|
+
drawGauge(ctx, config);
|
|
10123
|
+
drawInflectionValues(ctx, config);
|
|
10124
|
+
drawLabels(ctx, config);
|
|
10125
|
+
drawTitle(ctx, config);
|
|
10126
|
+
}
|
|
10127
|
+
function drawGauge(ctx, config) {
|
|
10128
|
+
ctx.save();
|
|
10129
|
+
const gauge = config.gauge;
|
|
10130
|
+
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
10131
|
+
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
10132
|
+
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
10133
|
+
if (arcRadius < 0) {
|
|
10134
|
+
return;
|
|
10135
|
+
}
|
|
10136
|
+
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
10137
|
+
// Gauge background
|
|
10138
|
+
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
10139
|
+
ctx.beginPath();
|
|
10140
|
+
ctx.lineWidth = gauge.arcWidth;
|
|
10141
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
10142
|
+
ctx.stroke();
|
|
10143
|
+
// Gauge value
|
|
10144
|
+
ctx.strokeStyle = gauge.color;
|
|
10145
|
+
ctx.beginPath();
|
|
10146
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
10147
|
+
ctx.stroke();
|
|
10148
|
+
ctx.restore();
|
|
10149
|
+
}
|
|
10150
|
+
function drawBackground(ctx, config) {
|
|
10151
|
+
ctx.save();
|
|
10152
|
+
ctx.fillStyle = config.backgroundColor;
|
|
10153
|
+
ctx.fillRect(0, 0, config.width, config.height);
|
|
10154
|
+
ctx.restore();
|
|
10155
|
+
}
|
|
10156
|
+
function drawLabels(ctx, config) {
|
|
10157
|
+
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
10158
|
+
ctx.save();
|
|
10159
|
+
ctx.textAlign = "center";
|
|
10160
|
+
ctx.fillStyle = label.color;
|
|
10161
|
+
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
10162
|
+
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
10163
|
+
ctx.restore();
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
10166
|
+
function drawInflectionValues(ctx, config) {
|
|
10167
|
+
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
10168
|
+
for (const inflectionValue of config.inflectionValues) {
|
|
10169
|
+
ctx.save();
|
|
10170
|
+
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
10171
|
+
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
10172
|
+
ctx.lineWidth = 2;
|
|
10173
|
+
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
10174
|
+
ctx.beginPath();
|
|
10175
|
+
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
10176
|
+
ctx.lineTo(0, -height - 3);
|
|
10177
|
+
ctx.stroke();
|
|
10178
|
+
ctx.textAlign = "center";
|
|
10179
|
+
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
10180
|
+
ctx.fillStyle = inflectionValue.color;
|
|
10181
|
+
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
10182
|
+
ctx.fillText(inflectionValue.label, 0, textY);
|
|
10183
|
+
ctx.restore();
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
function drawTitle(ctx, config) {
|
|
10187
|
+
ctx.save();
|
|
10188
|
+
const title = config.title;
|
|
10189
|
+
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
10190
|
+
ctx.textBaseline = "middle";
|
|
10191
|
+
ctx.fillStyle = title.color;
|
|
10192
|
+
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
10193
|
+
ctx.restore();
|
|
10194
|
+
}
|
|
10195
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
10196
|
+
const maxValue = runtime.maxValue;
|
|
10197
|
+
const minValue = runtime.minValue;
|
|
10198
|
+
const gaugeValue = runtime.gaugeValue;
|
|
10199
|
+
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
10200
|
+
const gaugeArcWidth = gaugeRect.width / 6;
|
|
10201
|
+
const gaugePercentage = gaugeValue
|
|
10202
|
+
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
10203
|
+
: 0;
|
|
10204
|
+
const gaugeValuePosition = {
|
|
10205
|
+
x: boundingRect.width / 2,
|
|
10206
|
+
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
10065
10207
|
};
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
return this.chartRuntime.background;
|
|
10208
|
+
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
10209
|
+
// Scale down the font size if the gaugeRect is too small
|
|
10210
|
+
if (gaugeRect.height < 300) {
|
|
10211
|
+
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
10071
10212
|
}
|
|
10072
|
-
|
|
10073
|
-
|
|
10213
|
+
// Scale down the font size if the text is too long
|
|
10214
|
+
const maxTextWidth = gaugeRect.width / 2;
|
|
10215
|
+
const gaugeLabel = gaugeValue?.label || "-";
|
|
10216
|
+
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
10217
|
+
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
10074
10218
|
}
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10219
|
+
const minLabelPosition = {
|
|
10220
|
+
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
10221
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10222
|
+
};
|
|
10223
|
+
const maxLabelPosition = {
|
|
10224
|
+
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
10225
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10226
|
+
};
|
|
10227
|
+
const textColor = chartMutedFontColor(runtime.background);
|
|
10228
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
10229
|
+
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
10230
|
+
if (runtime.title.text) {
|
|
10231
|
+
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
10081
10232
|
}
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10233
|
+
switch (runtime.title.align) {
|
|
10234
|
+
case "right":
|
|
10235
|
+
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
10236
|
+
break;
|
|
10237
|
+
case "center":
|
|
10238
|
+
x = (boundingRect.width - titleWidth) / 2;
|
|
10239
|
+
break;
|
|
10240
|
+
case "left":
|
|
10241
|
+
default:
|
|
10242
|
+
x = CHART_PADDING$1;
|
|
10243
|
+
break;
|
|
10244
|
+
}
|
|
10245
|
+
return {
|
|
10246
|
+
width: boundingRect.width,
|
|
10247
|
+
height: boundingRect.height,
|
|
10248
|
+
title: {
|
|
10249
|
+
label: runtime.title.text ?? "",
|
|
10250
|
+
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
10251
|
+
textPosition: {
|
|
10252
|
+
x,
|
|
10253
|
+
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
10254
|
+
},
|
|
10255
|
+
color: runtime.title.color ?? textColor,
|
|
10256
|
+
bold: runtime.title.bold,
|
|
10257
|
+
italic: runtime.title.italic,
|
|
10258
|
+
},
|
|
10259
|
+
backgroundColor: runtime.background,
|
|
10260
|
+
gauge: {
|
|
10261
|
+
rect: gaugeRect,
|
|
10262
|
+
arcWidth: gaugeArcWidth,
|
|
10263
|
+
percentage: clip(gaugePercentage, 0, 1),
|
|
10264
|
+
color: getGaugeColor(runtime),
|
|
10265
|
+
},
|
|
10266
|
+
inflectionValues,
|
|
10267
|
+
gaugeValue: {
|
|
10268
|
+
label: gaugeLabel,
|
|
10269
|
+
textPosition: gaugeValuePosition,
|
|
10270
|
+
fontSize: gaugeValueFontSize,
|
|
10271
|
+
color: textColor,
|
|
10272
|
+
},
|
|
10273
|
+
minLabel: {
|
|
10274
|
+
label: runtime.minValue.label,
|
|
10275
|
+
textPosition: minLabelPosition,
|
|
10276
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10277
|
+
color: textColor,
|
|
10278
|
+
},
|
|
10279
|
+
maxLabel: {
|
|
10280
|
+
label: runtime.maxValue.label,
|
|
10281
|
+
textPosition: maxLabelPosition,
|
|
10282
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10283
|
+
color: textColor,
|
|
10284
|
+
},
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
/**
|
|
10288
|
+
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
10289
|
+
* space for the title and labels.
|
|
10290
|
+
*/
|
|
10291
|
+
function getGaugeRect(boundingRect, title) {
|
|
10292
|
+
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
10293
|
+
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
10294
|
+
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
10295
|
+
let gaugeWidth;
|
|
10296
|
+
let gaugeHeight;
|
|
10297
|
+
if (drawWidth > 2 * drawHeight) {
|
|
10298
|
+
gaugeWidth = 2 * drawHeight;
|
|
10299
|
+
gaugeHeight = drawHeight;
|
|
10300
|
+
}
|
|
10301
|
+
else {
|
|
10302
|
+
gaugeWidth = drawWidth;
|
|
10303
|
+
gaugeHeight = drawWidth / 2;
|
|
10304
|
+
}
|
|
10305
|
+
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
10306
|
+
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
10307
|
+
return {
|
|
10308
|
+
x: gaugeX,
|
|
10309
|
+
y: gaugeY,
|
|
10310
|
+
width: gaugeWidth,
|
|
10311
|
+
height: gaugeHeight,
|
|
10312
|
+
};
|
|
10313
|
+
}
|
|
10314
|
+
/**
|
|
10315
|
+
* 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).
|
|
10316
|
+
*
|
|
10317
|
+
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
10318
|
+
*/
|
|
10319
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
10320
|
+
const maxValue = runtime.maxValue;
|
|
10321
|
+
const minValue = runtime.minValue;
|
|
10322
|
+
const gaugeCircleCenter = {
|
|
10323
|
+
x: gaugeRect.x + gaugeRect.width / 2,
|
|
10324
|
+
y: gaugeRect.y + gaugeRect.height,
|
|
10325
|
+
};
|
|
10326
|
+
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
10327
|
+
const inflectionValues = [];
|
|
10328
|
+
const inflectionValuesTextRects = [];
|
|
10329
|
+
for (const inflectionValue of runtime.inflectionValues) {
|
|
10330
|
+
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
10331
|
+
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
10332
|
+
const angle = Math.PI - Math.PI * percentage;
|
|
10333
|
+
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
10334
|
+
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
10335
|
+
gaugeCircleCenter.x, // center of the gauge circle
|
|
10336
|
+
gaugeCircleCenter.y, // center of the gauge circle
|
|
10337
|
+
labelWidth + 2, // width of the text + some margin
|
|
10338
|
+
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
10339
|
+
);
|
|
10340
|
+
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
10341
|
+
? GAUGE_LABELS_FONT_SIZE
|
|
10342
|
+
: 0;
|
|
10343
|
+
inflectionValuesTextRects.push(textRect);
|
|
10344
|
+
inflectionValues.push({
|
|
10345
|
+
rotation: angle,
|
|
10346
|
+
label: inflectionValue.label,
|
|
10347
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10348
|
+
color: textColor,
|
|
10349
|
+
offset,
|
|
10102
10350
|
});
|
|
10103
10351
|
}
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10352
|
+
return inflectionValues;
|
|
10353
|
+
}
|
|
10354
|
+
function getGaugeColor(runtime) {
|
|
10355
|
+
const gaugeValue = runtime.gaugeValue?.value;
|
|
10356
|
+
if (gaugeValue === undefined) {
|
|
10357
|
+
return GAUGE_BACKGROUND_COLOR;
|
|
10108
10358
|
}
|
|
10109
|
-
|
|
10110
|
-
const
|
|
10111
|
-
if (
|
|
10112
|
-
|
|
10113
|
-
if (chartData.options?.plugins?.title) {
|
|
10114
|
-
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
10115
|
-
}
|
|
10359
|
+
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
10360
|
+
const inflectionValue = runtime.inflectionValues[i];
|
|
10361
|
+
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
10362
|
+
return runtime.colors[i];
|
|
10116
10363
|
}
|
|
10117
|
-
else {
|
|
10118
|
-
|
|
10364
|
+
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
10365
|
+
return runtime.colors[i];
|
|
10119
10366
|
}
|
|
10120
|
-
this.chart.config.options = chartData.options;
|
|
10121
|
-
this.chart.update();
|
|
10122
10367
|
}
|
|
10368
|
+
return runtime.colors.at(-1);
|
|
10369
|
+
}
|
|
10370
|
+
function getSegmentsOfRectangle(rectangle) {
|
|
10371
|
+
return [
|
|
10372
|
+
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
10373
|
+
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
10374
|
+
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
10375
|
+
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
10376
|
+
];
|
|
10377
|
+
}
|
|
10378
|
+
/**
|
|
10379
|
+
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
10380
|
+
* is not handled.
|
|
10381
|
+
*/
|
|
10382
|
+
function doSegmentIntersect(segment1, segment2) {
|
|
10383
|
+
const A = segment1.start;
|
|
10384
|
+
const B = segment1.end;
|
|
10385
|
+
const C = segment2.start;
|
|
10386
|
+
const D = segment2.end;
|
|
10387
|
+
/**
|
|
10388
|
+
* Line segment intersection algorithm
|
|
10389
|
+
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
10390
|
+
*/
|
|
10391
|
+
function ccw(a, b, c) {
|
|
10392
|
+
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
10393
|
+
}
|
|
10394
|
+
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
10395
|
+
}
|
|
10396
|
+
function doRectanglesIntersect(rect1, rect2) {
|
|
10397
|
+
const segments1 = getSegmentsOfRectangle(rect1);
|
|
10398
|
+
const segments2 = getSegmentsOfRectangle(rect2);
|
|
10399
|
+
for (const segment1 of segments1) {
|
|
10400
|
+
for (const segment2 of segments2) {
|
|
10401
|
+
if (doSegmentIntersect(segment1, segment2)) {
|
|
10402
|
+
return true;
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
return false;
|
|
10407
|
+
}
|
|
10408
|
+
/**
|
|
10409
|
+
* Get the rectangle that is tangent to a circle at a given angle.
|
|
10410
|
+
*
|
|
10411
|
+
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
10412
|
+
*/
|
|
10413
|
+
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
10414
|
+
const cos = Math.cos(angle);
|
|
10415
|
+
const sin = Math.sin(angle);
|
|
10416
|
+
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
10417
|
+
const x = cos * radius;
|
|
10418
|
+
const y = sin * radius;
|
|
10419
|
+
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
10420
|
+
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
10421
|
+
const y2 = cos * (rectWidth / 2);
|
|
10422
|
+
const bottomRight = {
|
|
10423
|
+
x: x + x2 + circleCenterX,
|
|
10424
|
+
y: circleCenterY - (y - y2),
|
|
10425
|
+
};
|
|
10426
|
+
const bottomLeft = {
|
|
10427
|
+
x: x - x2 + circleCenterX,
|
|
10428
|
+
y: circleCenterY - (y + y2),
|
|
10429
|
+
};
|
|
10430
|
+
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
10431
|
+
const xp = cos * (radius + rectHeight);
|
|
10432
|
+
const yp = sin * (radius + rectHeight);
|
|
10433
|
+
const topLeft = {
|
|
10434
|
+
x: xp - x2 + circleCenterX,
|
|
10435
|
+
y: circleCenterY - (yp + y2),
|
|
10436
|
+
};
|
|
10437
|
+
const topRight = {
|
|
10438
|
+
x: xp + x2 + circleCenterX,
|
|
10439
|
+
y: circleCenterY - (yp - y2),
|
|
10440
|
+
};
|
|
10441
|
+
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
10123
10442
|
}
|
|
10124
10443
|
|
|
10125
10444
|
/**
|
|
@@ -10701,6 +11020,155 @@ class ScorecardChartConfigBuilder {
|
|
|
10701
11020
|
}
|
|
10702
11021
|
}
|
|
10703
11022
|
|
|
11023
|
+
const CHART_COMMON_OPTIONS = {
|
|
11024
|
+
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
11025
|
+
responsive: true, // will resize when its container is resized
|
|
11026
|
+
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
11027
|
+
elements: {
|
|
11028
|
+
line: {
|
|
11029
|
+
fill: false, // do not fill the area under line charts
|
|
11030
|
+
},
|
|
11031
|
+
point: {
|
|
11032
|
+
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
11033
|
+
},
|
|
11034
|
+
},
|
|
11035
|
+
animation: false,
|
|
11036
|
+
};
|
|
11037
|
+
function truncateLabel(label) {
|
|
11038
|
+
if (!label) {
|
|
11039
|
+
return "";
|
|
11040
|
+
}
|
|
11041
|
+
if (label.length > MAX_CHAR_LABEL) {
|
|
11042
|
+
return label.substring(0, MAX_CHAR_LABEL) + "…";
|
|
11043
|
+
}
|
|
11044
|
+
return label;
|
|
11045
|
+
}
|
|
11046
|
+
function chartToImage(runtime, figure, type) {
|
|
11047
|
+
// wrap the canvas in a div with a fixed size because chart.js would
|
|
11048
|
+
// fill the whole page otherwise
|
|
11049
|
+
const div = document.createElement("div");
|
|
11050
|
+
div.style.width = `${figure.width}px`;
|
|
11051
|
+
div.style.height = `${figure.height}px`;
|
|
11052
|
+
const canvas = document.createElement("canvas");
|
|
11053
|
+
div.append(canvas);
|
|
11054
|
+
canvas.setAttribute("width", figure.width.toString());
|
|
11055
|
+
canvas.setAttribute("height", figure.height.toString());
|
|
11056
|
+
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
11057
|
+
document.body.append(div);
|
|
11058
|
+
if ("chartJsConfig" in runtime) {
|
|
11059
|
+
const config = deepCopy(runtime.chartJsConfig);
|
|
11060
|
+
config.plugins = [backgroundColorChartJSPlugin];
|
|
11061
|
+
const Chart = getChartJSConstructor();
|
|
11062
|
+
const chart = new Chart(canvas, config);
|
|
11063
|
+
const imgContent = chart.toBase64Image();
|
|
11064
|
+
chart.destroy();
|
|
11065
|
+
div.remove();
|
|
11066
|
+
return imgContent;
|
|
11067
|
+
}
|
|
11068
|
+
else if (type === "scorecard") {
|
|
11069
|
+
const design = getScorecardConfiguration(figure, runtime);
|
|
11070
|
+
drawScoreChart(design, canvas);
|
|
11071
|
+
const imgContent = canvas.toDataURL();
|
|
11072
|
+
div.remove();
|
|
11073
|
+
return imgContent;
|
|
11074
|
+
}
|
|
11075
|
+
else if (type === "gauge") {
|
|
11076
|
+
drawGaugeChart(canvas, runtime);
|
|
11077
|
+
const imgContent = canvas.toDataURL();
|
|
11078
|
+
div.remove();
|
|
11079
|
+
return imgContent;
|
|
11080
|
+
}
|
|
11081
|
+
return undefined;
|
|
11082
|
+
}
|
|
11083
|
+
/**
|
|
11084
|
+
* Custom chart.js plugin to set the background color of the canvas
|
|
11085
|
+
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
11086
|
+
*/
|
|
11087
|
+
const backgroundColorChartJSPlugin = {
|
|
11088
|
+
id: "customCanvasBackgroundColor",
|
|
11089
|
+
beforeDraw: (chart) => {
|
|
11090
|
+
const { ctx } = chart;
|
|
11091
|
+
ctx.save();
|
|
11092
|
+
ctx.globalCompositeOperation = "destination-over";
|
|
11093
|
+
ctx.fillStyle = "#ffffff";
|
|
11094
|
+
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
11095
|
+
ctx.restore();
|
|
11096
|
+
},
|
|
11097
|
+
};
|
|
11098
|
+
/** Return window.Chart, making sure all our extensions are loaded in ChartJS */
|
|
11099
|
+
function getChartJSConstructor() {
|
|
11100
|
+
if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
|
|
11101
|
+
window.Chart.register(chartShowValuesPlugin);
|
|
11102
|
+
window.Chart.register(waterfallLinesPlugin);
|
|
11103
|
+
}
|
|
11104
|
+
return window.Chart;
|
|
11105
|
+
}
|
|
11106
|
+
|
|
11107
|
+
class ChartJsComponent extends Component {
|
|
11108
|
+
static template = "o-spreadsheet-ChartJsComponent";
|
|
11109
|
+
static props = {
|
|
11110
|
+
figure: Object,
|
|
11111
|
+
};
|
|
11112
|
+
canvas = useRef("graphContainer");
|
|
11113
|
+
chart;
|
|
11114
|
+
currentRuntime;
|
|
11115
|
+
get background() {
|
|
11116
|
+
return this.chartRuntime.background;
|
|
11117
|
+
}
|
|
11118
|
+
get canvasStyle() {
|
|
11119
|
+
return `background-color: ${this.background}`;
|
|
11120
|
+
}
|
|
11121
|
+
get chartRuntime() {
|
|
11122
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
|
|
11123
|
+
if (!("chartJsConfig" in runtime)) {
|
|
11124
|
+
throw new Error("Unsupported chart runtime");
|
|
11125
|
+
}
|
|
11126
|
+
return runtime;
|
|
11127
|
+
}
|
|
11128
|
+
setup() {
|
|
11129
|
+
onMounted(() => {
|
|
11130
|
+
const runtime = this.chartRuntime;
|
|
11131
|
+
this.currentRuntime = runtime;
|
|
11132
|
+
// Note: chartJS modify the runtime in place, so it's important to give it a copy
|
|
11133
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11134
|
+
});
|
|
11135
|
+
onWillUnmount(() => this.chart?.destroy());
|
|
11136
|
+
useEffect(() => {
|
|
11137
|
+
const runtime = this.chartRuntime;
|
|
11138
|
+
if (runtime !== this.currentRuntime) {
|
|
11139
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
11140
|
+
this.chart?.destroy();
|
|
11141
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11142
|
+
}
|
|
11143
|
+
else {
|
|
11144
|
+
this.updateChartJs(deepCopy(runtime));
|
|
11145
|
+
}
|
|
11146
|
+
this.currentRuntime = runtime;
|
|
11147
|
+
}
|
|
11148
|
+
});
|
|
11149
|
+
}
|
|
11150
|
+
createChart(chartData) {
|
|
11151
|
+
const canvas = this.canvas.el;
|
|
11152
|
+
const ctx = canvas.getContext("2d");
|
|
11153
|
+
const Chart = getChartJSConstructor();
|
|
11154
|
+
this.chart = new Chart(ctx, chartData);
|
|
11155
|
+
}
|
|
11156
|
+
updateChartJs(chartRuntime) {
|
|
11157
|
+
const chartData = chartRuntime.chartJsConfig;
|
|
11158
|
+
if (chartData.data && chartData.data.datasets) {
|
|
11159
|
+
this.chart.data = chartData.data;
|
|
11160
|
+
if (chartData.options?.plugins?.title) {
|
|
11161
|
+
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
else {
|
|
11165
|
+
this.chart.data.datasets = [];
|
|
11166
|
+
}
|
|
11167
|
+
this.chart.config.options = chartData.options;
|
|
11168
|
+
this.chart.update();
|
|
11169
|
+
}
|
|
11170
|
+
}
|
|
11171
|
+
|
|
10704
11172
|
class ScorecardChart extends Component {
|
|
10705
11173
|
static template = "o-spreadsheet-ScorecardChart";
|
|
10706
11174
|
static props = {
|
|
@@ -10732,6 +11200,7 @@ class ScorecardChart extends Component {
|
|
|
10732
11200
|
const autoCompleteProviders = new Registry();
|
|
10733
11201
|
|
|
10734
11202
|
autoCompleteProviders.add("dataValidation", {
|
|
11203
|
+
displayAllOnInitialContent: true,
|
|
10735
11204
|
getProposals(tokenAtCursor, content) {
|
|
10736
11205
|
if (content.startsWith("=")) {
|
|
10737
11206
|
return [];
|
|
@@ -21028,6 +21497,15 @@ class AbstractComposerStore extends SpreadsheetStore {
|
|
|
21028
21497
|
const exactMatch = proposals?.find((p) => p.text === tokenAtCursor.value);
|
|
21029
21498
|
// remove tokens that are likely to be other parts of the formula that slipped in the token if it's a string
|
|
21030
21499
|
const searchTerm = tokenAtCursor.value.replace(/[ ,\(\)]/g, "");
|
|
21500
|
+
if (this._currentContent === this.initialContent &&
|
|
21501
|
+
provider.displayAllOnInitialContent &&
|
|
21502
|
+
proposals?.length) {
|
|
21503
|
+
return {
|
|
21504
|
+
proposals,
|
|
21505
|
+
selectProposal: provider.selectProposal,
|
|
21506
|
+
autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
|
|
21507
|
+
};
|
|
21508
|
+
}
|
|
21031
21509
|
if (exactMatch && this._currentContent !== this.initialContent) {
|
|
21032
21510
|
// this means the user has chosen a proposal
|
|
21033
21511
|
return;
|
|
@@ -22153,7 +22631,7 @@ autofillRulesRegistry
|
|
|
22153
22631
|
condition: (cell) => !cell.isFormula &&
|
|
22154
22632
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
|
|
22155
22633
|
alphaNumericValueRegExp.test(cell.content),
|
|
22156
|
-
generateRule: (cell, cells) => {
|
|
22634
|
+
generateRule: (cell, cells, direction) => {
|
|
22157
22635
|
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
22158
22636
|
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
22159
22637
|
const numberPostfixLength = cell.content.length - prefix.length;
|
|
@@ -22161,7 +22639,10 @@ autofillRulesRegistry
|
|
|
22161
22639
|
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
22162
22640
|
.filter((cell) => prefix === (cell.value ?? "").toString().match(stringPrefixRegExp)[0])
|
|
22163
22641
|
.map((cell) => parseInt((cell.value ?? "").toString().match(numberPostfixRegExp)[0]));
|
|
22164
|
-
|
|
22642
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22643
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22644
|
+
increment = -increment;
|
|
22645
|
+
}
|
|
22165
22646
|
return {
|
|
22166
22647
|
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
22167
22648
|
prefix,
|
|
@@ -22224,10 +22705,13 @@ autofillRulesRegistry
|
|
|
22224
22705
|
.add("increment_number", {
|
|
22225
22706
|
condition: (cell) => !cell.isFormula &&
|
|
22226
22707
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22227
|
-
generateRule: (cell, cells) => {
|
|
22708
|
+
generateRule: (cell, cells, direction) => {
|
|
22228
22709
|
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22229
22710
|
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22230
|
-
|
|
22711
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22712
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22713
|
+
increment = -increment;
|
|
22714
|
+
}
|
|
22231
22715
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22232
22716
|
return {
|
|
22233
22717
|
type: "INCREMENT_MODIFIER",
|
|
@@ -22271,343 +22755,6 @@ function getDateIntervals(dates) {
|
|
|
22271
22755
|
|
|
22272
22756
|
const cellPopoverRegistry = new Registry();
|
|
22273
22757
|
|
|
22274
|
-
const GAUGE_PADDING_SIDE = 30;
|
|
22275
|
-
const GAUGE_PADDING_TOP = 10;
|
|
22276
|
-
const GAUGE_PADDING_BOTTOM = 20;
|
|
22277
|
-
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
22278
|
-
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
22279
|
-
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
22280
|
-
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
22281
|
-
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
22282
|
-
function drawGaugeChart(canvas, runtime) {
|
|
22283
|
-
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
22284
|
-
canvas.width = canvasBoundingRect.width;
|
|
22285
|
-
canvas.height = canvasBoundingRect.height;
|
|
22286
|
-
const ctx = canvas.getContext("2d");
|
|
22287
|
-
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
22288
|
-
drawBackground(ctx, config);
|
|
22289
|
-
drawGauge(ctx, config);
|
|
22290
|
-
drawInflectionValues(ctx, config);
|
|
22291
|
-
drawLabels(ctx, config);
|
|
22292
|
-
drawTitle(ctx, config);
|
|
22293
|
-
}
|
|
22294
|
-
function drawGauge(ctx, config) {
|
|
22295
|
-
ctx.save();
|
|
22296
|
-
const gauge = config.gauge;
|
|
22297
|
-
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
22298
|
-
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
22299
|
-
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
22300
|
-
if (arcRadius < 0) {
|
|
22301
|
-
return;
|
|
22302
|
-
}
|
|
22303
|
-
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
22304
|
-
// Gauge background
|
|
22305
|
-
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
22306
|
-
ctx.beginPath();
|
|
22307
|
-
ctx.lineWidth = gauge.arcWidth;
|
|
22308
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
22309
|
-
ctx.stroke();
|
|
22310
|
-
// Gauge value
|
|
22311
|
-
ctx.strokeStyle = gauge.color;
|
|
22312
|
-
ctx.beginPath();
|
|
22313
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
22314
|
-
ctx.stroke();
|
|
22315
|
-
ctx.restore();
|
|
22316
|
-
}
|
|
22317
|
-
function drawBackground(ctx, config) {
|
|
22318
|
-
ctx.save();
|
|
22319
|
-
ctx.fillStyle = config.backgroundColor;
|
|
22320
|
-
ctx.fillRect(0, 0, config.width, config.height);
|
|
22321
|
-
ctx.restore();
|
|
22322
|
-
}
|
|
22323
|
-
function drawLabels(ctx, config) {
|
|
22324
|
-
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
22325
|
-
ctx.save();
|
|
22326
|
-
ctx.textAlign = "center";
|
|
22327
|
-
ctx.fillStyle = label.color;
|
|
22328
|
-
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
22329
|
-
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
22330
|
-
ctx.restore();
|
|
22331
|
-
}
|
|
22332
|
-
}
|
|
22333
|
-
function drawInflectionValues(ctx, config) {
|
|
22334
|
-
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
22335
|
-
for (const inflectionValue of config.inflectionValues) {
|
|
22336
|
-
ctx.save();
|
|
22337
|
-
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
22338
|
-
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
22339
|
-
ctx.lineWidth = 2;
|
|
22340
|
-
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
22341
|
-
ctx.beginPath();
|
|
22342
|
-
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
22343
|
-
ctx.lineTo(0, -height - 3);
|
|
22344
|
-
ctx.stroke();
|
|
22345
|
-
ctx.textAlign = "center";
|
|
22346
|
-
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
22347
|
-
ctx.fillStyle = inflectionValue.color;
|
|
22348
|
-
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
22349
|
-
ctx.fillText(inflectionValue.label, 0, textY);
|
|
22350
|
-
ctx.restore();
|
|
22351
|
-
}
|
|
22352
|
-
}
|
|
22353
|
-
function drawTitle(ctx, config) {
|
|
22354
|
-
ctx.save();
|
|
22355
|
-
const title = config.title;
|
|
22356
|
-
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
22357
|
-
ctx.textBaseline = "middle";
|
|
22358
|
-
ctx.fillStyle = title.color;
|
|
22359
|
-
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
22360
|
-
ctx.restore();
|
|
22361
|
-
}
|
|
22362
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
22363
|
-
const maxValue = runtime.maxValue;
|
|
22364
|
-
const minValue = runtime.minValue;
|
|
22365
|
-
const gaugeValue = runtime.gaugeValue;
|
|
22366
|
-
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
22367
|
-
const gaugeArcWidth = gaugeRect.width / 6;
|
|
22368
|
-
const gaugePercentage = gaugeValue
|
|
22369
|
-
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
22370
|
-
: 0;
|
|
22371
|
-
const gaugeValuePosition = {
|
|
22372
|
-
x: boundingRect.width / 2,
|
|
22373
|
-
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
22374
|
-
};
|
|
22375
|
-
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
22376
|
-
// Scale down the font size if the gaugeRect is too small
|
|
22377
|
-
if (gaugeRect.height < 300) {
|
|
22378
|
-
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
22379
|
-
}
|
|
22380
|
-
// Scale down the font size if the text is too long
|
|
22381
|
-
const maxTextWidth = gaugeRect.width / 2;
|
|
22382
|
-
const gaugeLabel = gaugeValue?.label || "-";
|
|
22383
|
-
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
22384
|
-
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
22385
|
-
}
|
|
22386
|
-
const minLabelPosition = {
|
|
22387
|
-
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
22388
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22389
|
-
};
|
|
22390
|
-
const maxLabelPosition = {
|
|
22391
|
-
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
22392
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22393
|
-
};
|
|
22394
|
-
const textColor = chartMutedFontColor(runtime.background);
|
|
22395
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
22396
|
-
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
22397
|
-
if (runtime.title.text) {
|
|
22398
|
-
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
22399
|
-
}
|
|
22400
|
-
switch (runtime.title.align) {
|
|
22401
|
-
case "right":
|
|
22402
|
-
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
22403
|
-
break;
|
|
22404
|
-
case "center":
|
|
22405
|
-
x = (boundingRect.width - titleWidth) / 2;
|
|
22406
|
-
break;
|
|
22407
|
-
case "left":
|
|
22408
|
-
default:
|
|
22409
|
-
x = CHART_PADDING$1;
|
|
22410
|
-
break;
|
|
22411
|
-
}
|
|
22412
|
-
return {
|
|
22413
|
-
width: boundingRect.width,
|
|
22414
|
-
height: boundingRect.height,
|
|
22415
|
-
title: {
|
|
22416
|
-
label: runtime.title.text ?? "",
|
|
22417
|
-
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
22418
|
-
textPosition: {
|
|
22419
|
-
x,
|
|
22420
|
-
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
22421
|
-
},
|
|
22422
|
-
color: runtime.title.color ?? textColor,
|
|
22423
|
-
bold: runtime.title.bold,
|
|
22424
|
-
italic: runtime.title.italic,
|
|
22425
|
-
},
|
|
22426
|
-
backgroundColor: runtime.background,
|
|
22427
|
-
gauge: {
|
|
22428
|
-
rect: gaugeRect,
|
|
22429
|
-
arcWidth: gaugeArcWidth,
|
|
22430
|
-
percentage: clip(gaugePercentage, 0, 1),
|
|
22431
|
-
color: getGaugeColor(runtime),
|
|
22432
|
-
},
|
|
22433
|
-
inflectionValues,
|
|
22434
|
-
gaugeValue: {
|
|
22435
|
-
label: gaugeLabel,
|
|
22436
|
-
textPosition: gaugeValuePosition,
|
|
22437
|
-
fontSize: gaugeValueFontSize,
|
|
22438
|
-
color: textColor,
|
|
22439
|
-
},
|
|
22440
|
-
minLabel: {
|
|
22441
|
-
label: runtime.minValue.label,
|
|
22442
|
-
textPosition: minLabelPosition,
|
|
22443
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22444
|
-
color: textColor,
|
|
22445
|
-
},
|
|
22446
|
-
maxLabel: {
|
|
22447
|
-
label: runtime.maxValue.label,
|
|
22448
|
-
textPosition: maxLabelPosition,
|
|
22449
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22450
|
-
color: textColor,
|
|
22451
|
-
},
|
|
22452
|
-
};
|
|
22453
|
-
}
|
|
22454
|
-
/**
|
|
22455
|
-
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
22456
|
-
* space for the title and labels.
|
|
22457
|
-
*/
|
|
22458
|
-
function getGaugeRect(boundingRect, title) {
|
|
22459
|
-
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
22460
|
-
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
22461
|
-
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
22462
|
-
let gaugeWidth;
|
|
22463
|
-
let gaugeHeight;
|
|
22464
|
-
if (drawWidth > 2 * drawHeight) {
|
|
22465
|
-
gaugeWidth = 2 * drawHeight;
|
|
22466
|
-
gaugeHeight = drawHeight;
|
|
22467
|
-
}
|
|
22468
|
-
else {
|
|
22469
|
-
gaugeWidth = drawWidth;
|
|
22470
|
-
gaugeHeight = drawWidth / 2;
|
|
22471
|
-
}
|
|
22472
|
-
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
22473
|
-
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
22474
|
-
return {
|
|
22475
|
-
x: gaugeX,
|
|
22476
|
-
y: gaugeY,
|
|
22477
|
-
width: gaugeWidth,
|
|
22478
|
-
height: gaugeHeight,
|
|
22479
|
-
};
|
|
22480
|
-
}
|
|
22481
|
-
/**
|
|
22482
|
-
* 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).
|
|
22483
|
-
*
|
|
22484
|
-
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
22485
|
-
*/
|
|
22486
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
22487
|
-
const maxValue = runtime.maxValue;
|
|
22488
|
-
const minValue = runtime.minValue;
|
|
22489
|
-
const gaugeCircleCenter = {
|
|
22490
|
-
x: gaugeRect.x + gaugeRect.width / 2,
|
|
22491
|
-
y: gaugeRect.y + gaugeRect.height,
|
|
22492
|
-
};
|
|
22493
|
-
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
22494
|
-
const inflectionValues = [];
|
|
22495
|
-
const inflectionValuesTextRects = [];
|
|
22496
|
-
for (const inflectionValue of runtime.inflectionValues) {
|
|
22497
|
-
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
22498
|
-
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
22499
|
-
const angle = Math.PI - Math.PI * percentage;
|
|
22500
|
-
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
22501
|
-
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
22502
|
-
gaugeCircleCenter.x, // center of the gauge circle
|
|
22503
|
-
gaugeCircleCenter.y, // center of the gauge circle
|
|
22504
|
-
labelWidth + 2, // width of the text + some margin
|
|
22505
|
-
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
22506
|
-
);
|
|
22507
|
-
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
22508
|
-
? GAUGE_LABELS_FONT_SIZE
|
|
22509
|
-
: 0;
|
|
22510
|
-
inflectionValuesTextRects.push(textRect);
|
|
22511
|
-
inflectionValues.push({
|
|
22512
|
-
rotation: angle,
|
|
22513
|
-
label: inflectionValue.label,
|
|
22514
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22515
|
-
color: textColor,
|
|
22516
|
-
offset,
|
|
22517
|
-
});
|
|
22518
|
-
}
|
|
22519
|
-
return inflectionValues;
|
|
22520
|
-
}
|
|
22521
|
-
function getGaugeColor(runtime) {
|
|
22522
|
-
const gaugeValue = runtime.gaugeValue?.value;
|
|
22523
|
-
if (gaugeValue === undefined) {
|
|
22524
|
-
return GAUGE_BACKGROUND_COLOR;
|
|
22525
|
-
}
|
|
22526
|
-
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
22527
|
-
const inflectionValue = runtime.inflectionValues[i];
|
|
22528
|
-
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
22529
|
-
return runtime.colors[i];
|
|
22530
|
-
}
|
|
22531
|
-
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
22532
|
-
return runtime.colors[i];
|
|
22533
|
-
}
|
|
22534
|
-
}
|
|
22535
|
-
return runtime.colors.at(-1);
|
|
22536
|
-
}
|
|
22537
|
-
function getSegmentsOfRectangle(rectangle) {
|
|
22538
|
-
return [
|
|
22539
|
-
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
22540
|
-
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
22541
|
-
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
22542
|
-
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
22543
|
-
];
|
|
22544
|
-
}
|
|
22545
|
-
/**
|
|
22546
|
-
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
22547
|
-
* is not handled.
|
|
22548
|
-
*/
|
|
22549
|
-
function doSegmentIntersect(segment1, segment2) {
|
|
22550
|
-
const A = segment1.start;
|
|
22551
|
-
const B = segment1.end;
|
|
22552
|
-
const C = segment2.start;
|
|
22553
|
-
const D = segment2.end;
|
|
22554
|
-
/**
|
|
22555
|
-
* Line segment intersection algorithm
|
|
22556
|
-
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
22557
|
-
*/
|
|
22558
|
-
function ccw(a, b, c) {
|
|
22559
|
-
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
22560
|
-
}
|
|
22561
|
-
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
22562
|
-
}
|
|
22563
|
-
function doRectanglesIntersect(rect1, rect2) {
|
|
22564
|
-
const segments1 = getSegmentsOfRectangle(rect1);
|
|
22565
|
-
const segments2 = getSegmentsOfRectangle(rect2);
|
|
22566
|
-
for (const segment1 of segments1) {
|
|
22567
|
-
for (const segment2 of segments2) {
|
|
22568
|
-
if (doSegmentIntersect(segment1, segment2)) {
|
|
22569
|
-
return true;
|
|
22570
|
-
}
|
|
22571
|
-
}
|
|
22572
|
-
}
|
|
22573
|
-
return false;
|
|
22574
|
-
}
|
|
22575
|
-
/**
|
|
22576
|
-
* Get the rectangle that is tangent to a circle at a given angle.
|
|
22577
|
-
*
|
|
22578
|
-
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
22579
|
-
*/
|
|
22580
|
-
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
22581
|
-
const cos = Math.cos(angle);
|
|
22582
|
-
const sin = Math.sin(angle);
|
|
22583
|
-
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
22584
|
-
const x = cos * radius;
|
|
22585
|
-
const y = sin * radius;
|
|
22586
|
-
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
22587
|
-
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
22588
|
-
const y2 = cos * (rectWidth / 2);
|
|
22589
|
-
const bottomRight = {
|
|
22590
|
-
x: x + x2 + circleCenterX,
|
|
22591
|
-
y: circleCenterY - (y - y2),
|
|
22592
|
-
};
|
|
22593
|
-
const bottomLeft = {
|
|
22594
|
-
x: x - x2 + circleCenterX,
|
|
22595
|
-
y: circleCenterY - (y + y2),
|
|
22596
|
-
};
|
|
22597
|
-
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
22598
|
-
const xp = cos * (radius + rectHeight);
|
|
22599
|
-
const yp = sin * (radius + rectHeight);
|
|
22600
|
-
const topLeft = {
|
|
22601
|
-
x: xp - x2 + circleCenterX,
|
|
22602
|
-
y: circleCenterY - (yp + y2),
|
|
22603
|
-
};
|
|
22604
|
-
const topRight = {
|
|
22605
|
-
x: xp + x2 + circleCenterX,
|
|
22606
|
-
y: circleCenterY - (yp - y2),
|
|
22607
|
-
};
|
|
22608
|
-
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
22609
|
-
}
|
|
22610
|
-
|
|
22611
22758
|
class GaugeChartComponent extends Component {
|
|
22612
22759
|
static template = "o-spreadsheet-GaugeChartComponent";
|
|
22613
22760
|
canvas = useRef("chartContainer");
|
|
@@ -22640,81 +22787,6 @@ function toXlsxHexColor(color) {
|
|
|
22640
22787
|
return color;
|
|
22641
22788
|
}
|
|
22642
22789
|
|
|
22643
|
-
const CHART_COMMON_OPTIONS = {
|
|
22644
|
-
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
22645
|
-
responsive: true, // will resize when its container is resized
|
|
22646
|
-
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
22647
|
-
elements: {
|
|
22648
|
-
line: {
|
|
22649
|
-
fill: false, // do not fill the area under line charts
|
|
22650
|
-
},
|
|
22651
|
-
point: {
|
|
22652
|
-
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
22653
|
-
},
|
|
22654
|
-
},
|
|
22655
|
-
animation: false,
|
|
22656
|
-
};
|
|
22657
|
-
function truncateLabel(label) {
|
|
22658
|
-
if (!label) {
|
|
22659
|
-
return "";
|
|
22660
|
-
}
|
|
22661
|
-
if (label.length > MAX_CHAR_LABEL) {
|
|
22662
|
-
return label.substring(0, MAX_CHAR_LABEL) + "…";
|
|
22663
|
-
}
|
|
22664
|
-
return label;
|
|
22665
|
-
}
|
|
22666
|
-
function chartToImage(runtime, figure, type) {
|
|
22667
|
-
// wrap the canvas in a div with a fixed size because chart.js would
|
|
22668
|
-
// fill the whole page otherwise
|
|
22669
|
-
const div = document.createElement("div");
|
|
22670
|
-
div.style.width = `${figure.width}px`;
|
|
22671
|
-
div.style.height = `${figure.height}px`;
|
|
22672
|
-
const canvas = document.createElement("canvas");
|
|
22673
|
-
div.append(canvas);
|
|
22674
|
-
canvas.setAttribute("width", figure.width.toString());
|
|
22675
|
-
canvas.setAttribute("height", figure.height.toString());
|
|
22676
|
-
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
22677
|
-
document.body.append(div);
|
|
22678
|
-
if ("chartJsConfig" in runtime) {
|
|
22679
|
-
const config = deepCopy(runtime.chartJsConfig);
|
|
22680
|
-
config.plugins = [backgroundColorChartJSPlugin];
|
|
22681
|
-
const chart = new window.Chart(canvas, config);
|
|
22682
|
-
const imgContent = chart.toBase64Image();
|
|
22683
|
-
chart.destroy();
|
|
22684
|
-
div.remove();
|
|
22685
|
-
return imgContent;
|
|
22686
|
-
}
|
|
22687
|
-
else if (type === "scorecard") {
|
|
22688
|
-
const design = getScorecardConfiguration(figure, runtime);
|
|
22689
|
-
drawScoreChart(design, canvas);
|
|
22690
|
-
const imgContent = canvas.toDataURL();
|
|
22691
|
-
div.remove();
|
|
22692
|
-
return imgContent;
|
|
22693
|
-
}
|
|
22694
|
-
else if (type === "gauge") {
|
|
22695
|
-
drawGaugeChart(canvas, runtime);
|
|
22696
|
-
const imgContent = canvas.toDataURL();
|
|
22697
|
-
div.remove();
|
|
22698
|
-
return imgContent;
|
|
22699
|
-
}
|
|
22700
|
-
return undefined;
|
|
22701
|
-
}
|
|
22702
|
-
/**
|
|
22703
|
-
* Custom chart.js plugin to set the background color of the canvas
|
|
22704
|
-
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
22705
|
-
*/
|
|
22706
|
-
const backgroundColorChartJSPlugin = {
|
|
22707
|
-
id: "customCanvasBackgroundColor",
|
|
22708
|
-
beforeDraw: (chart) => {
|
|
22709
|
-
const { ctx } = chart;
|
|
22710
|
-
ctx.save();
|
|
22711
|
-
ctx.globalCompositeOperation = "destination-over";
|
|
22712
|
-
ctx.fillStyle = "#ffffff";
|
|
22713
|
-
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
22714
|
-
ctx.restore();
|
|
22715
|
-
},
|
|
22716
|
-
};
|
|
22717
|
-
|
|
22718
22790
|
/**
|
|
22719
22791
|
* Represent a raw XML string
|
|
22720
22792
|
*/
|
|
@@ -22776,6 +22848,7 @@ const DRAWING_NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
|
22776
22848
|
const CONTENT_TYPES = {
|
|
22777
22849
|
workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
22778
22850
|
sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
|
22851
|
+
metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
|
|
22779
22852
|
sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
|
22780
22853
|
styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
|
22781
22854
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
@@ -22788,6 +22861,7 @@ const CONTENT_TYPES = {
|
|
|
22788
22861
|
const XLSX_RELATION_TYPE = {
|
|
22789
22862
|
document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
22790
22863
|
sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
|
22864
|
+
metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
|
|
22791
22865
|
sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
|
|
22792
22866
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
22793
22867
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -22797,6 +22871,7 @@ const XLSX_RELATION_TYPE = {
|
|
|
22797
22871
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22798
22872
|
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22799
22873
|
};
|
|
22874
|
+
const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
|
|
22800
22875
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22801
22876
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
22802
22877
|
/**
|
|
@@ -24316,16 +24391,25 @@ function addRelsToFile(relsFiles, path, rel) {
|
|
|
24316
24391
|
}
|
|
24317
24392
|
return id;
|
|
24318
24393
|
}
|
|
24394
|
+
const globalReverseLookup = new WeakMap();
|
|
24319
24395
|
function pushElement(property, propertyList) {
|
|
24320
|
-
let
|
|
24321
|
-
|
|
24322
|
-
|
|
24323
|
-
|
|
24324
|
-
|
|
24396
|
+
let reverseLookup = globalReverseLookup.get(propertyList);
|
|
24397
|
+
if (!reverseLookup) {
|
|
24398
|
+
reverseLookup = new Map();
|
|
24399
|
+
for (let i = 0; i < propertyList.length; i++) {
|
|
24400
|
+
const canonical = getCanonicalRepresentation(propertyList[i]);
|
|
24401
|
+
reverseLookup.set(canonical, i);
|
|
24325
24402
|
}
|
|
24403
|
+
globalReverseLookup.set(propertyList, reverseLookup);
|
|
24326
24404
|
}
|
|
24327
|
-
|
|
24328
|
-
|
|
24405
|
+
const canonical = getCanonicalRepresentation(property);
|
|
24406
|
+
if (reverseLookup.has(canonical)) {
|
|
24407
|
+
return reverseLookup.get(canonical);
|
|
24408
|
+
}
|
|
24409
|
+
const maxId = propertyList.length;
|
|
24410
|
+
propertyList.push(property);
|
|
24411
|
+
reverseLookup.set(canonical, maxId);
|
|
24412
|
+
return maxId;
|
|
24329
24413
|
}
|
|
24330
24414
|
const chartIds = [];
|
|
24331
24415
|
/**
|
|
@@ -25362,29 +25446,34 @@ function convertPivotTableConfig(pivotTable) {
|
|
|
25362
25446
|
* In all the sheets, replace the table-only references in the formula cells with standard references.
|
|
25363
25447
|
*/
|
|
25364
25448
|
function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
25365
|
-
for (let
|
|
25366
|
-
const tables = xlsxSheets.find((s) => s.sheetName ===
|
|
25449
|
+
for (let tableSheet of convertedSheets) {
|
|
25450
|
+
const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
|
|
25367
25451
|
for (let table of tables) {
|
|
25368
25452
|
const tabRef = table.name + "[";
|
|
25369
|
-
for (let
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25453
|
+
for (let sheet of convertedSheets) {
|
|
25454
|
+
for (let xc in sheet.cells) {
|
|
25455
|
+
const cell = sheet.cells[xc];
|
|
25456
|
+
let cellContent = sheet.cells[xc];
|
|
25457
|
+
if (cell && cellContent && cellContent.startsWith("=")) {
|
|
25458
|
+
let refIndex;
|
|
25459
|
+
while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
|
|
25460
|
+
let endIndex = refIndex + tabRef.length;
|
|
25461
|
+
let openBrackets = 1;
|
|
25462
|
+
while (openBrackets > 0 && endIndex < cellContent.length) {
|
|
25463
|
+
if (cellContent[endIndex] === "[") {
|
|
25464
|
+
openBrackets++;
|
|
25465
|
+
}
|
|
25466
|
+
else if (cellContent[endIndex] === "]") {
|
|
25467
|
+
openBrackets--;
|
|
25468
|
+
}
|
|
25469
|
+
endIndex++;
|
|
25470
|
+
}
|
|
25471
|
+
let reference = cellContent.slice(refIndex + tabRef.length, endIndex - 1);
|
|
25472
|
+
const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
|
|
25473
|
+
const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
|
|
25474
|
+
cellContent =
|
|
25475
|
+
cellContent.slice(0, refIndex) + convertedRef + cellContent.slice(endIndex);
|
|
25381
25476
|
}
|
|
25382
|
-
reference = reference.slice(0, endIndex);
|
|
25383
|
-
const convertedRef = convertTableReference(reference, table, xc);
|
|
25384
|
-
cellContent =
|
|
25385
|
-
cellContent.slice(0, refIndex) +
|
|
25386
|
-
convertedRef +
|
|
25387
|
-
cellContent.slice(tabRef.length + refIndex + endIndex + 1);
|
|
25388
25477
|
}
|
|
25389
25478
|
sheet.cells[xc] = cellContent;
|
|
25390
25479
|
}
|
|
@@ -25393,11 +25482,17 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25393
25482
|
}
|
|
25394
25483
|
}
|
|
25395
25484
|
/**
|
|
25396
|
-
* Convert table-specific references in formulas into standard references.
|
|
25485
|
+
* Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
|
|
25486
|
+
* and of keywords determining the rows of the table to reference.
|
|
25397
25487
|
*
|
|
25398
25488
|
* A reference in a table can have the form (only the part between brackets should be given to this function):
|
|
25399
25489
|
* - tableName[colName] : reference to the whole column "colName"
|
|
25490
|
+
* - tableName[#keyword] : reference to the whatever row the keyword refers to
|
|
25400
25491
|
* - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
|
|
25492
|
+
* - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
|
|
25493
|
+
* - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
|
|
25494
|
+
* - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
|
|
25495
|
+
*
|
|
25401
25496
|
*
|
|
25402
25497
|
* The available keywords are :
|
|
25403
25498
|
* - #All : all the column (including totals)
|
|
@@ -25405,58 +25500,109 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25405
25500
|
* - #Headers : only the header of the column
|
|
25406
25501
|
* - #Totals : only the totals of the column
|
|
25407
25502
|
* - #This Row : only the element in the same row as the cell
|
|
25503
|
+
*
|
|
25504
|
+
* Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
|
|
25408
25505
|
*/
|
|
25409
|
-
function convertTableReference(expr, table, cellXc) {
|
|
25410
|
-
|
|
25506
|
+
function convertTableReference(sheetPrefix, expr, table, cellXc) {
|
|
25507
|
+
// TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
|
|
25508
|
+
// contain # or , characters. But that's probably an edge case that we can ignore for now.
|
|
25509
|
+
const parts = expr.split(",").map((part) => part.trim());
|
|
25411
25510
|
const tableZone = toZone(table.ref);
|
|
25412
|
-
const
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
25417
|
-
|
|
25418
|
-
|
|
25419
|
-
|
|
25420
|
-
|
|
25421
|
-
|
|
25422
|
-
|
|
25511
|
+
const colIndexes = [];
|
|
25512
|
+
const rowIndexes = [];
|
|
25513
|
+
const foundKeywords = [];
|
|
25514
|
+
for (const part of parts) {
|
|
25515
|
+
if (removeBrackets(part).startsWith("#")) {
|
|
25516
|
+
const keyWord = removeBrackets(part);
|
|
25517
|
+
foundKeywords.push(keyWord);
|
|
25518
|
+
switch (keyWord) {
|
|
25519
|
+
case "#All":
|
|
25520
|
+
rowIndexes.push(tableZone.top, tableZone.bottom);
|
|
25521
|
+
break;
|
|
25522
|
+
case "#Data":
|
|
25523
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25524
|
+
const bottom = table.totalsRowCount
|
|
25525
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25526
|
+
: tableZone.bottom;
|
|
25527
|
+
rowIndexes.push(top, bottom);
|
|
25528
|
+
break;
|
|
25529
|
+
case "#This Row":
|
|
25530
|
+
rowIndexes.push(toCartesian(cellXc).row);
|
|
25531
|
+
break;
|
|
25532
|
+
case "#Headers":
|
|
25533
|
+
if (!table.headerRowCount) {
|
|
25534
|
+
return CellErrorType.InvalidReference;
|
|
25535
|
+
}
|
|
25536
|
+
rowIndexes.push(tableZone.top);
|
|
25537
|
+
break;
|
|
25538
|
+
case "#Totals":
|
|
25539
|
+
if (!table.totalsRowCount) {
|
|
25540
|
+
return CellErrorType.InvalidReference;
|
|
25541
|
+
}
|
|
25542
|
+
rowIndexes.push(tableZone.bottom);
|
|
25543
|
+
break;
|
|
25544
|
+
}
|
|
25423
25545
|
}
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
|
|
25428
|
-
|
|
25429
|
-
|
|
25430
|
-
|
|
25431
|
-
|
|
25432
|
-
|
|
25433
|
-
|
|
25434
|
-
|
|
25435
|
-
|
|
25436
|
-
|
|
25437
|
-
|
|
25438
|
-
|
|
25439
|
-
|
|
25440
|
-
|
|
25441
|
-
if (!table.headerRowCount) {
|
|
25442
|
-
isReferencedZoneValid = false;
|
|
25443
|
-
}
|
|
25444
|
-
break;
|
|
25445
|
-
case "#Totals":
|
|
25446
|
-
refZone.top = refZone.bottom = tableZone.bottom;
|
|
25447
|
-
if (!table.totalsRowCount) {
|
|
25448
|
-
isReferencedZoneValid = false;
|
|
25546
|
+
else {
|
|
25547
|
+
const columns = part
|
|
25548
|
+
.split(":")
|
|
25549
|
+
.map((part) => part.trim())
|
|
25550
|
+
.map(removeBrackets);
|
|
25551
|
+
if (colIndexes.length) {
|
|
25552
|
+
return CellErrorType.InvalidReference;
|
|
25553
|
+
}
|
|
25554
|
+
const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
|
|
25555
|
+
if (colRelativeIndex === -1) {
|
|
25556
|
+
return CellErrorType.InvalidReference;
|
|
25557
|
+
}
|
|
25558
|
+
colIndexes.push(colRelativeIndex + tableZone.left);
|
|
25559
|
+
if (columns[1]) {
|
|
25560
|
+
const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
|
|
25561
|
+
if (colRelativeIndex2 === -1) {
|
|
25562
|
+
return CellErrorType.InvalidReference;
|
|
25449
25563
|
}
|
|
25450
|
-
|
|
25564
|
+
colIndexes.push(colRelativeIndex2 + tableZone.left);
|
|
25565
|
+
}
|
|
25451
25566
|
}
|
|
25452
|
-
const colRef = refElements[1].slice(1, refElements[1].length - 1);
|
|
25453
|
-
const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
|
|
25454
|
-
refZone.left = refZone.right = colRelativeIndex + tableZone.left;
|
|
25455
25567
|
}
|
|
25456
|
-
if (!
|
|
25568
|
+
if (!areKeywordsCompatible(foundKeywords)) {
|
|
25457
25569
|
return CellErrorType.InvalidReference;
|
|
25458
25570
|
}
|
|
25459
|
-
|
|
25571
|
+
if (rowIndexes.length === 0) {
|
|
25572
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25573
|
+
const bottom = table.totalsRowCount
|
|
25574
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25575
|
+
: tableZone.bottom;
|
|
25576
|
+
rowIndexes.push(top, bottom);
|
|
25577
|
+
}
|
|
25578
|
+
if (colIndexes.length === 0) {
|
|
25579
|
+
colIndexes.push(tableZone.left, tableZone.right);
|
|
25580
|
+
}
|
|
25581
|
+
const refZone = {
|
|
25582
|
+
top: Math.min(...rowIndexes),
|
|
25583
|
+
left: Math.min(...colIndexes),
|
|
25584
|
+
bottom: Math.max(...rowIndexes),
|
|
25585
|
+
right: Math.max(...colIndexes),
|
|
25586
|
+
};
|
|
25587
|
+
return sheetPrefix + zoneToXc(refZone);
|
|
25588
|
+
}
|
|
25589
|
+
function removeBrackets(str) {
|
|
25590
|
+
return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
|
|
25591
|
+
}
|
|
25592
|
+
function areKeywordsCompatible(keywords) {
|
|
25593
|
+
if (keywords.length < 2) {
|
|
25594
|
+
return true;
|
|
25595
|
+
}
|
|
25596
|
+
else if (keywords.length > 2) {
|
|
25597
|
+
return false;
|
|
25598
|
+
}
|
|
25599
|
+
else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
|
|
25600
|
+
return true;
|
|
25601
|
+
}
|
|
25602
|
+
else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
|
|
25603
|
+
return true;
|
|
25604
|
+
}
|
|
25605
|
+
return false;
|
|
25460
25606
|
}
|
|
25461
25607
|
|
|
25462
25608
|
// -------------------------------------
|
|
@@ -26110,7 +26256,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
|
|
|
26110
26256
|
title: { text: chartTitle },
|
|
26111
26257
|
type: CHART_TYPE_CONVERSION_MAP[chartType],
|
|
26112
26258
|
dataSets: this.extractChartDatasets(this.querySelectorAll(rootChartElement, `c:${chartType}`), chartType),
|
|
26113
|
-
labelRange: this.
|
|
26259
|
+
labelRange: this.extractLabelRange(chartType, rootChartElement),
|
|
26114
26260
|
backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
|
|
26115
26261
|
default: "ffffff",
|
|
26116
26262
|
}).asString(),
|
|
@@ -26122,6 +26268,13 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
|
|
|
26122
26268
|
};
|
|
26123
26269
|
})[0];
|
|
26124
26270
|
}
|
|
26271
|
+
extractLabelRange(chartType, rootChartElement) {
|
|
26272
|
+
if (chartType === "scatterChart") {
|
|
26273
|
+
return (this.extractChildTextContent(rootChartElement, `c:ser c:strRef c:f`) ||
|
|
26274
|
+
this.extractChildTextContent(rootChartElement, `c:ser c:numRef c:f`));
|
|
26275
|
+
}
|
|
26276
|
+
return this.extractChildTextContent(rootChartElement, `c:ser c:cat c:f`);
|
|
26277
|
+
}
|
|
26125
26278
|
extractComboChart(chartElement) {
|
|
26126
26279
|
// Title can be separated into multiple xml elements (for styling and such), we only import the text
|
|
26127
26280
|
const chartTitle = this.mapOnElements({ parent: chartElement, query: "c:title a:t" }, (textElement) => {
|
|
@@ -28624,11 +28777,12 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
|
|
|
28624
28777
|
}
|
|
28625
28778
|
let missingTimeAdapterAlreadyWarned = false;
|
|
28626
28779
|
function isLuxonTimeAdapterInstalled() {
|
|
28627
|
-
|
|
28780
|
+
const Chart = getChartJSConstructor();
|
|
28781
|
+
if (!Chart) {
|
|
28628
28782
|
return false;
|
|
28629
28783
|
}
|
|
28630
28784
|
// @ts-ignore
|
|
28631
|
-
const adapter = new
|
|
28785
|
+
const adapter = new Chart._adapters._date({});
|
|
28632
28786
|
const isInstalled = adapter._id === "luxon";
|
|
28633
28787
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
28634
28788
|
missingTimeAdapterAlreadyWarned = true;
|
|
@@ -32325,10 +32479,6 @@ class Popover extends Component {
|
|
|
32325
32479
|
this.currentDisplayValue = newDisplay;
|
|
32326
32480
|
if (!anchor)
|
|
32327
32481
|
return;
|
|
32328
|
-
el.style.top = "";
|
|
32329
|
-
el.style.left = "";
|
|
32330
|
-
el.style["max-height"] = "";
|
|
32331
|
-
el.style["max-width"] = "";
|
|
32332
32482
|
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
32333
32483
|
let elDims = {
|
|
32334
32484
|
width: el.getBoundingClientRect().width,
|
|
@@ -33866,6 +34016,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
|
|
|
33866
34016
|
drawScoreChart: drawScoreChart,
|
|
33867
34017
|
formatChartDatasetValue: formatChartDatasetValue,
|
|
33868
34018
|
formatTickValue: formatTickValue,
|
|
34019
|
+
getChartJSConstructor: getChartJSConstructor,
|
|
33869
34020
|
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
33870
34021
|
getDefinedAxis: getDefinedAxis,
|
|
33871
34022
|
getPieColors: getPieColors,
|
|
@@ -36125,6 +36276,7 @@ const irregularityMap = {
|
|
|
36125
36276
|
fingerprintStore.enable();
|
|
36126
36277
|
}
|
|
36127
36278
|
},
|
|
36279
|
+
isReadonlyAllowed: true,
|
|
36128
36280
|
icon: "o-spreadsheet-Icon.IRREGULARITY_MAP",
|
|
36129
36281
|
};
|
|
36130
36282
|
const viewFormulas = {
|
|
@@ -37749,6 +37901,9 @@ class GenericChartConfigPanel extends Component {
|
|
|
37749
37901
|
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
|
|
37750
37902
|
dataSets: this.dataSeriesRanges,
|
|
37751
37903
|
});
|
|
37904
|
+
if (this.state.datasetDispatchResult.isSuccessful) {
|
|
37905
|
+
this.dataSeriesRanges = this.env.model.getters.getChartDefinition(this.props.figureId).dataSets;
|
|
37906
|
+
}
|
|
37752
37907
|
}
|
|
37753
37908
|
getDataSeriesRanges() {
|
|
37754
37909
|
return this.dataSeriesRanges;
|
|
@@ -40388,8 +40543,7 @@ css /* scss */ `
|
|
|
40388
40543
|
}
|
|
40389
40544
|
|
|
40390
40545
|
.o-composer-assistant {
|
|
40391
|
-
|
|
40392
|
-
margin: 1px 4px;
|
|
40546
|
+
margin-top: 1px;
|
|
40393
40547
|
|
|
40394
40548
|
.o-semi-bold {
|
|
40395
40549
|
/* FIXME: to remove in favor of Bootstrap
|
|
@@ -40440,10 +40594,11 @@ class Composer extends Component {
|
|
|
40440
40594
|
});
|
|
40441
40595
|
compositionActive = false;
|
|
40442
40596
|
spreadsheetRect = useSpreadsheetRect();
|
|
40443
|
-
get
|
|
40597
|
+
get assistantStyleProperties() {
|
|
40444
40598
|
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
40445
40599
|
const assistantStyle = {};
|
|
40446
|
-
|
|
40600
|
+
const minWidth = Math.min(this.props.rect?.width || Infinity, ASSISTANT_WIDTH);
|
|
40601
|
+
assistantStyle["min-width"] = `${minWidth}px`;
|
|
40447
40602
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
40448
40603
|
const proposalsHaveDescription = proposals?.some((proposal) => proposal.description);
|
|
40449
40604
|
if (this.functionDescriptionState.showDescription || proposalsHaveDescription) {
|
|
@@ -40467,13 +40622,29 @@ class Composer extends Component {
|
|
|
40467
40622
|
}
|
|
40468
40623
|
}
|
|
40469
40624
|
else {
|
|
40470
|
-
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
40625
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom - 1}px`; // -1: margin
|
|
40471
40626
|
if (composerRect.left + ASSISTANT_WIDTH + SCROLLBAR_WIDTH + CLOSE_ICON_RADIUS >
|
|
40472
40627
|
this.spreadsheetRect.width) {
|
|
40473
40628
|
assistantStyle.right = `${CLOSE_ICON_RADIUS}px`;
|
|
40474
40629
|
}
|
|
40475
40630
|
}
|
|
40476
|
-
return
|
|
40631
|
+
return assistantStyle;
|
|
40632
|
+
}
|
|
40633
|
+
get assistantStyle() {
|
|
40634
|
+
const allProperties = this.assistantStyleProperties;
|
|
40635
|
+
return cssPropertiesToCss({
|
|
40636
|
+
"max-height": allProperties["max-height"],
|
|
40637
|
+
width: allProperties["width"],
|
|
40638
|
+
"min-width": allProperties["min-width"],
|
|
40639
|
+
});
|
|
40640
|
+
}
|
|
40641
|
+
get assistantContainerStyle() {
|
|
40642
|
+
const allProperties = this.assistantStyleProperties;
|
|
40643
|
+
return cssPropertiesToCss({
|
|
40644
|
+
top: allProperties["top"],
|
|
40645
|
+
right: allProperties["right"],
|
|
40646
|
+
transform: allProperties["transform"],
|
|
40647
|
+
});
|
|
40477
40648
|
}
|
|
40478
40649
|
// we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
|
|
40479
40650
|
shouldProcessInputEvents = false;
|
|
@@ -46410,9 +46581,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46410
46581
|
pivot: this.draft,
|
|
46411
46582
|
});
|
|
46412
46583
|
this.draft = null;
|
|
46413
|
-
if (!this.alreadyNotified &&
|
|
46414
|
-
!this.isDynamicPivotInViewport() &&
|
|
46415
|
-
this.isStaticPivotInViewport()) {
|
|
46584
|
+
if (!this.alreadyNotified && this.isUpdatedPivotVisibleInViewportOnlyAsStaticPivot()) {
|
|
46416
46585
|
const formulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46417
46586
|
const pivotExample = `=PIVOT(${formulaId})`;
|
|
46418
46587
|
this.alreadyNotified = true;
|
|
@@ -46468,29 +46637,33 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46468
46637
|
this.applyUpdate();
|
|
46469
46638
|
}
|
|
46470
46639
|
}
|
|
46471
|
-
|
|
46472
|
-
|
|
46473
|
-
|
|
46474
|
-
|
|
46475
|
-
|
|
46476
|
-
|
|
46477
|
-
|
|
46478
|
-
}
|
|
46479
|
-
}
|
|
46480
|
-
}
|
|
46481
|
-
return false;
|
|
46482
|
-
}
|
|
46483
|
-
isStaticPivotInViewport() {
|
|
46640
|
+
/**
|
|
46641
|
+
* @returns true if the updated pivot is visible in the viewport only as a
|
|
46642
|
+
* static pivot and not as a dynamic pivot
|
|
46643
|
+
*/
|
|
46644
|
+
isUpdatedPivotVisibleInViewportOnlyAsStaticPivot() {
|
|
46645
|
+
let staticPivotCount = 0;
|
|
46646
|
+
const updatedPivotFormulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46484
46647
|
for (const position of this.getters.getVisibleCellPositions()) {
|
|
46485
46648
|
const cell = this.getters.getCell(position);
|
|
46486
46649
|
if (cell?.isFormula) {
|
|
46487
46650
|
const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
46488
|
-
|
|
46489
|
-
|
|
46651
|
+
const pivotFormulaId = pivotFunction?.args[0]?.value;
|
|
46652
|
+
if (pivotFunction && updatedPivotFormulaId === pivotFormulaId.toString()) {
|
|
46653
|
+
if (pivotFunction.functionName === "PIVOT") {
|
|
46654
|
+
// if we have at least one dynamic pivot visible inserted the viewport
|
|
46655
|
+
// we return false
|
|
46656
|
+
return false;
|
|
46657
|
+
}
|
|
46658
|
+
else {
|
|
46659
|
+
staticPivotCount++;
|
|
46660
|
+
}
|
|
46490
46661
|
}
|
|
46491
46662
|
}
|
|
46492
46663
|
}
|
|
46493
|
-
return
|
|
46664
|
+
// we return true if there are only static pivots visible inserted the viewport,
|
|
46665
|
+
// otherwise false
|
|
46666
|
+
return staticPivotCount > 0;
|
|
46494
46667
|
}
|
|
46495
46668
|
addDefaultDateTimeGranularity(fields, definition) {
|
|
46496
46669
|
const { columns, rows } = definition;
|
|
@@ -50309,6 +50482,9 @@ class ColResizer extends AbstractResizer {
|
|
|
50309
50482
|
this.MAX_SIZE_MARGIN = 90;
|
|
50310
50483
|
this.MIN_ELEMENT_SIZE = MIN_COL_WIDTH;
|
|
50311
50484
|
}
|
|
50485
|
+
get sheetId() {
|
|
50486
|
+
return this.env.model.getters.getActiveSheetId();
|
|
50487
|
+
}
|
|
50312
50488
|
_getEvOffset(ev) {
|
|
50313
50489
|
return ev.offsetX;
|
|
50314
50490
|
}
|
|
@@ -50331,10 +50507,10 @@ class ColResizer extends AbstractResizer {
|
|
|
50331
50507
|
return this.env.model.getters.getEdgeScrollCol(position, position, position);
|
|
50332
50508
|
}
|
|
50333
50509
|
_getDimensionsInViewport(index) {
|
|
50334
|
-
return this.env.model.getters.getColDimensionsInViewport(this.
|
|
50510
|
+
return this.env.model.getters.getColDimensionsInViewport(this.sheetId, index);
|
|
50335
50511
|
}
|
|
50336
50512
|
_getElementSize(index) {
|
|
50337
|
-
return this.env.model.getters.getColSize(this.
|
|
50513
|
+
return this.env.model.getters.getColSize(this.sheetId, index);
|
|
50338
50514
|
}
|
|
50339
50515
|
_getMaxSize() {
|
|
50340
50516
|
return this.colResizerRef.el.clientWidth;
|
|
@@ -50345,7 +50521,7 @@ class ColResizer extends AbstractResizer {
|
|
|
50345
50521
|
const cols = this.env.model.getters.getActiveCols();
|
|
50346
50522
|
this.env.model.dispatch("RESIZE_COLUMNS_ROWS", {
|
|
50347
50523
|
dimension: "COL",
|
|
50348
|
-
sheetId: this.
|
|
50524
|
+
sheetId: this.sheetId,
|
|
50349
50525
|
elements: cols.has(index) ? [...cols] : [index],
|
|
50350
50526
|
size,
|
|
50351
50527
|
});
|
|
@@ -50358,7 +50534,7 @@ class ColResizer extends AbstractResizer {
|
|
|
50358
50534
|
elements.push(colIndex);
|
|
50359
50535
|
}
|
|
50360
50536
|
const result = this.env.model.dispatch("MOVE_COLUMNS_ROWS", {
|
|
50361
|
-
sheetId: this.
|
|
50537
|
+
sheetId: this.sheetId,
|
|
50362
50538
|
dimension: "COL",
|
|
50363
50539
|
base: this.state.base,
|
|
50364
50540
|
elements,
|
|
@@ -50377,7 +50553,7 @@ class ColResizer extends AbstractResizer {
|
|
|
50377
50553
|
_fitElementSize(index) {
|
|
50378
50554
|
const cols = this.env.model.getters.getActiveCols();
|
|
50379
50555
|
this.env.model.dispatch("AUTORESIZE_COLUMNS", {
|
|
50380
|
-
sheetId: this.
|
|
50556
|
+
sheetId: this.sheetId,
|
|
50381
50557
|
cols: cols.has(index) ? [...cols] : [index],
|
|
50382
50558
|
});
|
|
50383
50559
|
}
|
|
@@ -50388,7 +50564,7 @@ class ColResizer extends AbstractResizer {
|
|
|
50388
50564
|
return this.env.model.getters.getActiveCols();
|
|
50389
50565
|
}
|
|
50390
50566
|
_getPreviousVisibleElement(index) {
|
|
50391
|
-
const sheetId = this.
|
|
50567
|
+
const sheetId = this.sheetId;
|
|
50392
50568
|
let row;
|
|
50393
50569
|
for (row = index - 1; row >= 0; row--) {
|
|
50394
50570
|
if (!this.env.model.getters.isColHidden(sheetId, row)) {
|
|
@@ -50399,7 +50575,7 @@ class ColResizer extends AbstractResizer {
|
|
|
50399
50575
|
}
|
|
50400
50576
|
unhide(hiddenElements) {
|
|
50401
50577
|
this.env.model.dispatch("UNHIDE_COLUMNS_ROWS", {
|
|
50402
|
-
sheetId: this.
|
|
50578
|
+
sheetId: this.sheetId,
|
|
50403
50579
|
elements: hiddenElements,
|
|
50404
50580
|
dimension: "COL",
|
|
50405
50581
|
});
|
|
@@ -50415,7 +50591,7 @@ css /* scss */ `
|
|
|
50415
50591
|
left: 0;
|
|
50416
50592
|
right: 0;
|
|
50417
50593
|
width: ${HEADER_WIDTH}px;
|
|
50418
|
-
height: 100
|
|
50594
|
+
height: calc(100% - ${HEADER_HEIGHT + SCROLLBAR_WIDTH}px);
|
|
50419
50595
|
&.o-dragging {
|
|
50420
50596
|
cursor: grabbing;
|
|
50421
50597
|
}
|
|
@@ -50473,6 +50649,9 @@ class RowResizer extends AbstractResizer {
|
|
|
50473
50649
|
this.MIN_ELEMENT_SIZE = MIN_ROW_HEIGHT;
|
|
50474
50650
|
}
|
|
50475
50651
|
rowResizerRef;
|
|
50652
|
+
get sheetId() {
|
|
50653
|
+
return this.env.model.getters.getActiveSheetId();
|
|
50654
|
+
}
|
|
50476
50655
|
_getEvOffset(ev) {
|
|
50477
50656
|
return ev.offsetY;
|
|
50478
50657
|
}
|
|
@@ -50495,10 +50674,10 @@ class RowResizer extends AbstractResizer {
|
|
|
50495
50674
|
return this.env.model.getters.getEdgeScrollRow(position, position, position);
|
|
50496
50675
|
}
|
|
50497
50676
|
_getDimensionsInViewport(index) {
|
|
50498
|
-
return this.env.model.getters.getRowDimensionsInViewport(this.
|
|
50677
|
+
return this.env.model.getters.getRowDimensionsInViewport(this.sheetId, index);
|
|
50499
50678
|
}
|
|
50500
50679
|
_getElementSize(index) {
|
|
50501
|
-
return this.env.model.getters.getRowSize(this.
|
|
50680
|
+
return this.env.model.getters.getRowSize(this.sheetId, index);
|
|
50502
50681
|
}
|
|
50503
50682
|
_getMaxSize() {
|
|
50504
50683
|
return this.rowResizerRef.el.clientHeight;
|
|
@@ -50509,7 +50688,7 @@ class RowResizer extends AbstractResizer {
|
|
|
50509
50688
|
const rows = this.env.model.getters.getActiveRows();
|
|
50510
50689
|
this.env.model.dispatch("RESIZE_COLUMNS_ROWS", {
|
|
50511
50690
|
dimension: "ROW",
|
|
50512
|
-
sheetId: this.
|
|
50691
|
+
sheetId: this.sheetId,
|
|
50513
50692
|
elements: rows.has(index) ? [...rows] : [index],
|
|
50514
50693
|
size,
|
|
50515
50694
|
});
|
|
@@ -50522,7 +50701,7 @@ class RowResizer extends AbstractResizer {
|
|
|
50522
50701
|
elements.push(rowIndex);
|
|
50523
50702
|
}
|
|
50524
50703
|
const result = this.env.model.dispatch("MOVE_COLUMNS_ROWS", {
|
|
50525
|
-
sheetId: this.
|
|
50704
|
+
sheetId: this.sheetId,
|
|
50526
50705
|
dimension: "ROW",
|
|
50527
50706
|
base: this.state.base,
|
|
50528
50707
|
elements,
|
|
@@ -50541,7 +50720,7 @@ class RowResizer extends AbstractResizer {
|
|
|
50541
50720
|
_fitElementSize(index) {
|
|
50542
50721
|
const rows = this.env.model.getters.getActiveRows();
|
|
50543
50722
|
this.env.model.dispatch("AUTORESIZE_ROWS", {
|
|
50544
|
-
sheetId: this.
|
|
50723
|
+
sheetId: this.sheetId,
|
|
50545
50724
|
rows: rows.has(index) ? [...rows] : [index],
|
|
50546
50725
|
});
|
|
50547
50726
|
}
|
|
@@ -50552,7 +50731,7 @@ class RowResizer extends AbstractResizer {
|
|
|
50552
50731
|
return this.env.model.getters.getActiveRows();
|
|
50553
50732
|
}
|
|
50554
50733
|
_getPreviousVisibleElement(index) {
|
|
50555
|
-
const sheetId = this.
|
|
50734
|
+
const sheetId = this.sheetId;
|
|
50556
50735
|
let row;
|
|
50557
50736
|
for (row = index - 1; row >= 0; row--) {
|
|
50558
50737
|
if (!this.env.model.getters.isRowHidden(sheetId, row)) {
|
|
@@ -50563,7 +50742,7 @@ class RowResizer extends AbstractResizer {
|
|
|
50563
50742
|
}
|
|
50564
50743
|
unhide(hiddenElements) {
|
|
50565
50744
|
this.env.model.dispatch("UNHIDE_COLUMNS_ROWS", {
|
|
50566
|
-
sheetId: this.
|
|
50745
|
+
sheetId: this.sheetId,
|
|
50567
50746
|
dimension: "ROW",
|
|
50568
50747
|
elements: hiddenElements,
|
|
50569
50748
|
});
|
|
@@ -60330,6 +60509,7 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60330
60509
|
exportForExcel(data) {
|
|
60331
60510
|
for (const sheet of data.sheets) {
|
|
60332
60511
|
sheet.cellValues = {};
|
|
60512
|
+
sheet.formulaSpillRanges = {};
|
|
60333
60513
|
}
|
|
60334
60514
|
for (const position of this.evaluator.getEvaluatedPositions()) {
|
|
60335
60515
|
const evaluatedCell = this.evaluator.getEvaluatedCell(position);
|
|
@@ -60341,8 +60521,9 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60341
60521
|
const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
|
|
60342
60522
|
const formulaCell = this.getCorrespondingFormulaCell(position);
|
|
60343
60523
|
if (formulaCell) {
|
|
60524
|
+
const cell = this.getters.getCell(position);
|
|
60344
60525
|
isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
|
|
60345
|
-
isFormula = isExported;
|
|
60526
|
+
isFormula = isExported && cell?.content === formulaCell.content;
|
|
60346
60527
|
// If the cell contains a non-exported formula and that is evaluates to
|
|
60347
60528
|
// nothing* ,we don't export it.
|
|
60348
60529
|
// * non-falsy value are relevant and so are 0 and FALSE, which only leaves
|
|
@@ -60365,7 +60546,11 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60365
60546
|
content = !isExported ? newContent : exportedCellData;
|
|
60366
60547
|
}
|
|
60367
60548
|
exportedSheetData.cells[xc] = content;
|
|
60368
|
-
exportedSheetData.cellValues[xc] = value;
|
|
60549
|
+
exportedSheetData.cellValues[xc] = evaluatedCell.type !== "error" ? value : undefined;
|
|
60550
|
+
const spillZone = this.getSpreadZone(position);
|
|
60551
|
+
if (spillZone) {
|
|
60552
|
+
exportedSheetData.formulaSpillRanges[xc] = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
|
|
60553
|
+
}
|
|
60369
60554
|
}
|
|
60370
60555
|
}
|
|
60371
60556
|
/**
|
|
@@ -62572,7 +62757,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
62572
62757
|
getRule(cell, cells) {
|
|
62573
62758
|
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
62574
62759
|
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
62575
|
-
return rule && rule.generateRule(cell, cells);
|
|
62760
|
+
return rule && this.direction && rule.generateRule(cell, cells, this.direction);
|
|
62576
62761
|
}
|
|
62577
62762
|
/**
|
|
62578
62763
|
* Create the generator to be able to autofill the next cells.
|
|
@@ -67796,7 +67981,8 @@ class SheetViewPlugin extends UIPlugin {
|
|
|
67796
67981
|
? this.getters.getSheetViewVisibleCols()
|
|
67797
67982
|
: this.getters.getSheetViewVisibleRows();
|
|
67798
67983
|
const startIndex = visibleHeaders.findIndex((header) => referenceHeaderIndex >= header);
|
|
67799
|
-
|
|
67984
|
+
let endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
|
|
67985
|
+
endIndex = endIndex === -1 ? visibleHeaders.length : endIndex;
|
|
67800
67986
|
const relevantIndexes = visibleHeaders.slice(startIndex, endIndex);
|
|
67801
67987
|
let offset = 0;
|
|
67802
67988
|
for (const i of relevantIndexes) {
|
|
@@ -73102,7 +73288,7 @@ function numberRef(reference) {
|
|
|
73102
73288
|
`;
|
|
73103
73289
|
}
|
|
73104
73290
|
|
|
73105
|
-
function addFormula(formula, value) {
|
|
73291
|
+
function addFormula(formula, value, formulaSpillRange) {
|
|
73106
73292
|
if (!formula) {
|
|
73107
73293
|
return { attrs: [], node: escapeXml `` };
|
|
73108
73294
|
}
|
|
@@ -73110,10 +73296,17 @@ function addFormula(formula, value) {
|
|
|
73110
73296
|
if (type === undefined) {
|
|
73111
73297
|
return { attrs: [], node: escapeXml `` };
|
|
73112
73298
|
}
|
|
73113
|
-
const attrs = [
|
|
73299
|
+
const attrs = [
|
|
73300
|
+
["cm", "1"],
|
|
73301
|
+
["t", type],
|
|
73302
|
+
];
|
|
73114
73303
|
const XlsxFormula = adaptFormulaToExcel(formula);
|
|
73115
73304
|
const exportedValue = adaptFormulaValueToExcel(value);
|
|
73116
|
-
|
|
73305
|
+
// We treat all formulas as array formulas (a simple formula
|
|
73306
|
+
// is an array formula that spills on only one cell) to avoid
|
|
73307
|
+
// trying to detect spilling sub-formulas which is not a trivial task.
|
|
73308
|
+
let node;
|
|
73309
|
+
node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
|
|
73117
73310
|
return { attrs, node };
|
|
73118
73311
|
}
|
|
73119
73312
|
function addContent(content, sharedStrings, forceString = false) {
|
|
@@ -73896,7 +74089,7 @@ function addStyles(styles) {
|
|
|
73896
74089
|
}
|
|
73897
74090
|
if (alignAttrs.length > 0) {
|
|
73898
74091
|
attributes.push(["applyAlignment", "1"]); // for Libre Office
|
|
73899
|
-
styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)}
|
|
74092
|
+
styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)}><alignment ${formatAttributes(alignAttrs)} /></xf> `);
|
|
73900
74093
|
}
|
|
73901
74094
|
else {
|
|
73902
74095
|
styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)} />`);
|
|
@@ -74064,6 +74257,9 @@ function addColumns(cols) {
|
|
|
74064
74257
|
}
|
|
74065
74258
|
function addRows(construct, data, sheet) {
|
|
74066
74259
|
const rowNodes = [];
|
|
74260
|
+
const styles = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.styles));
|
|
74261
|
+
const borders = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.borders));
|
|
74262
|
+
const formats = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.formats));
|
|
74067
74263
|
for (let r = 0; r < sheet.rowNumber; r++) {
|
|
74068
74264
|
const rowAttrs = [["r", r + 1]];
|
|
74069
74265
|
const row = sheet.rows[r] || {};
|
|
@@ -74079,9 +74275,6 @@ function addRows(construct, data, sheet) {
|
|
|
74079
74275
|
if (row.collapsed) {
|
|
74080
74276
|
rowAttrs.push(["collapsed", 1]);
|
|
74081
74277
|
}
|
|
74082
|
-
const styles = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.styles));
|
|
74083
|
-
const borders = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.borders));
|
|
74084
|
-
const formats = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.formats));
|
|
74085
74278
|
const cellNodes = [];
|
|
74086
74279
|
for (let c = 0; c < sheet.colNumber; c++) {
|
|
74087
74280
|
const xc = toXC(c, r);
|
|
@@ -74103,7 +74296,7 @@ function addRows(construct, data, sheet) {
|
|
|
74103
74296
|
let cellNode = escapeXml ``;
|
|
74104
74297
|
// Either formula or static value inside the cell
|
|
74105
74298
|
if (content?.startsWith("=") && value !== undefined) {
|
|
74106
|
-
const res = addFormula(content, value);
|
|
74299
|
+
const res = addFormula(content, value, sheet.formulaSpillRanges[xc] ?? xc);
|
|
74107
74300
|
if (!res) {
|
|
74108
74301
|
continue;
|
|
74109
74302
|
}
|
|
@@ -74389,6 +74582,30 @@ function createWorksheets(data, construct) {
|
|
|
74389
74582
|
`;
|
|
74390
74583
|
files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
|
|
74391
74584
|
}
|
|
74585
|
+
const sheetMetadataXml = escapeXml /*xml*/ `
|
|
74586
|
+
<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
|
|
74587
|
+
<metadataTypes count="1">
|
|
74588
|
+
<metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
|
|
74589
|
+
pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
|
|
74590
|
+
clearComments="1" assign="1" coerce="1" cellMeta="1" />
|
|
74591
|
+
</metadataTypes>
|
|
74592
|
+
<futureMetadata name="XLDAPR" count="1">
|
|
74593
|
+
<bk>
|
|
74594
|
+
<extLst>
|
|
74595
|
+
<ext uri="{${ARRAY_FORMULA_URI}}">
|
|
74596
|
+
<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
|
|
74597
|
+
</ext>
|
|
74598
|
+
</extLst>
|
|
74599
|
+
</bk>
|
|
74600
|
+
</futureMetadata>
|
|
74601
|
+
<cellMetadata count="1">
|
|
74602
|
+
<bk>
|
|
74603
|
+
<rc t="1" v="0" />
|
|
74604
|
+
</bk>
|
|
74605
|
+
</cellMetadata>
|
|
74606
|
+
</metadata>
|
|
74607
|
+
`;
|
|
74608
|
+
files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
|
|
74392
74609
|
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74393
74610
|
type: XLSX_RELATION_TYPE.sharedStrings,
|
|
74394
74611
|
target: "sharedStrings.xml",
|
|
@@ -74397,6 +74614,10 @@ function createWorksheets(data, construct) {
|
|
|
74397
74614
|
type: XLSX_RELATION_TYPE.styles,
|
|
74398
74615
|
target: "styles.xml",
|
|
74399
74616
|
});
|
|
74617
|
+
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74618
|
+
type: XLSX_RELATION_TYPE.metadata,
|
|
74619
|
+
target: "metadata.xml",
|
|
74620
|
+
});
|
|
74400
74621
|
return files;
|
|
74401
74622
|
}
|
|
74402
74623
|
/**
|
|
@@ -75281,6 +75502,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
|
|
|
75281
75502
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, 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 };
|
|
75282
75503
|
|
|
75283
75504
|
|
|
75284
|
-
__info__.version = "18.1.
|
|
75285
|
-
__info__.date = "2025-
|
|
75286
|
-
__info__.hash = "
|
|
75505
|
+
__info__.version = "18.1.11";
|
|
75506
|
+
__info__.date = "2025-03-12T15:31:44.276Z";
|
|
75507
|
+
__info__.hash = "7de2363";
|