@odoo/o-spreadsheet 18.1.9 → 18.1.10
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 +722 -581
- package/dist/o-spreadsheet.d.ts +12 -3
- package/dist/o-spreadsheet.esm.js +722 -581
- package/dist/o-spreadsheet.iife.js +722 -581
- package/dist/o-spreadsheet.iife.min.js +397 -376
- package/dist/o_spreadsheet.xml +31 -26
- 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.10
|
|
6
|
+
* @date 2025-03-07T10:34:41.861Z
|
|
7
|
+
* @hash 31e4526
|
|
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';
|
|
@@ -6091,8 +6091,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6091
6091
|
if (zone.bottom !== zone.top && zone.left != zone.right) {
|
|
6092
6092
|
if (zone.right) {
|
|
6093
6093
|
for (let j = zone.left; j <= zone.right; ++j) {
|
|
6094
|
+
const datasetOptions = j === zone.left ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6094
6095
|
postProcessedRanges.push({
|
|
6095
|
-
...
|
|
6096
|
+
...datasetOptions,
|
|
6096
6097
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6097
6098
|
left: j,
|
|
6098
6099
|
right: j,
|
|
@@ -6104,8 +6105,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6104
6105
|
}
|
|
6105
6106
|
else {
|
|
6106
6107
|
for (let j = zone.top; j <= zone.bottom; ++j) {
|
|
6108
|
+
const datasetOptions = j === zone.top ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6107
6109
|
postProcessedRanges.push({
|
|
6108
|
-
...
|
|
6110
|
+
...datasetOptions,
|
|
6109
6111
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6110
6112
|
left: zone.left,
|
|
6111
6113
|
right: zone.right,
|
|
@@ -10056,70 +10058,341 @@ function getNextNonEmptyBar(bars, startIndex) {
|
|
|
10056
10058
|
return bars.find((bar, i) => i > startIndex && bar.height !== 0);
|
|
10057
10059
|
}
|
|
10058
10060
|
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10061
|
+
const GAUGE_PADDING_SIDE = 30;
|
|
10062
|
+
const GAUGE_PADDING_TOP = 10;
|
|
10063
|
+
const GAUGE_PADDING_BOTTOM = 20;
|
|
10064
|
+
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
10065
|
+
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
10066
|
+
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
10067
|
+
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
10068
|
+
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
10069
|
+
function drawGaugeChart(canvas, runtime) {
|
|
10070
|
+
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
10071
|
+
canvas.width = canvasBoundingRect.width;
|
|
10072
|
+
canvas.height = canvasBoundingRect.height;
|
|
10073
|
+
const ctx = canvas.getContext("2d");
|
|
10074
|
+
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
10075
|
+
drawBackground(ctx, config);
|
|
10076
|
+
drawGauge(ctx, config);
|
|
10077
|
+
drawInflectionValues(ctx, config);
|
|
10078
|
+
drawLabels(ctx, config);
|
|
10079
|
+
drawTitle(ctx, config);
|
|
10080
|
+
}
|
|
10081
|
+
function drawGauge(ctx, config) {
|
|
10082
|
+
ctx.save();
|
|
10083
|
+
const gauge = config.gauge;
|
|
10084
|
+
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
10085
|
+
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
10086
|
+
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
10087
|
+
if (arcRadius < 0) {
|
|
10088
|
+
return;
|
|
10089
|
+
}
|
|
10090
|
+
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
10091
|
+
// Gauge background
|
|
10092
|
+
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
10093
|
+
ctx.beginPath();
|
|
10094
|
+
ctx.lineWidth = gauge.arcWidth;
|
|
10095
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
10096
|
+
ctx.stroke();
|
|
10097
|
+
// Gauge value
|
|
10098
|
+
ctx.strokeStyle = gauge.color;
|
|
10099
|
+
ctx.beginPath();
|
|
10100
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
10101
|
+
ctx.stroke();
|
|
10102
|
+
ctx.restore();
|
|
10103
|
+
}
|
|
10104
|
+
function drawBackground(ctx, config) {
|
|
10105
|
+
ctx.save();
|
|
10106
|
+
ctx.fillStyle = config.backgroundColor;
|
|
10107
|
+
ctx.fillRect(0, 0, config.width, config.height);
|
|
10108
|
+
ctx.restore();
|
|
10109
|
+
}
|
|
10110
|
+
function drawLabels(ctx, config) {
|
|
10111
|
+
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
10112
|
+
ctx.save();
|
|
10113
|
+
ctx.textAlign = "center";
|
|
10114
|
+
ctx.fillStyle = label.color;
|
|
10115
|
+
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
10116
|
+
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
10117
|
+
ctx.restore();
|
|
10118
|
+
}
|
|
10119
|
+
}
|
|
10120
|
+
function drawInflectionValues(ctx, config) {
|
|
10121
|
+
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
10122
|
+
for (const inflectionValue of config.inflectionValues) {
|
|
10123
|
+
ctx.save();
|
|
10124
|
+
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
10125
|
+
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
10126
|
+
ctx.lineWidth = 2;
|
|
10127
|
+
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
10128
|
+
ctx.beginPath();
|
|
10129
|
+
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
10130
|
+
ctx.lineTo(0, -height - 3);
|
|
10131
|
+
ctx.stroke();
|
|
10132
|
+
ctx.textAlign = "center";
|
|
10133
|
+
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
10134
|
+
ctx.fillStyle = inflectionValue.color;
|
|
10135
|
+
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
10136
|
+
ctx.fillText(inflectionValue.label, 0, textY);
|
|
10137
|
+
ctx.restore();
|
|
10138
|
+
}
|
|
10139
|
+
}
|
|
10140
|
+
function drawTitle(ctx, config) {
|
|
10141
|
+
ctx.save();
|
|
10142
|
+
const title = config.title;
|
|
10143
|
+
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
10144
|
+
ctx.textBaseline = "middle";
|
|
10145
|
+
ctx.fillStyle = title.color;
|
|
10146
|
+
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
10147
|
+
ctx.restore();
|
|
10148
|
+
}
|
|
10149
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
10150
|
+
const maxValue = runtime.maxValue;
|
|
10151
|
+
const minValue = runtime.minValue;
|
|
10152
|
+
const gaugeValue = runtime.gaugeValue;
|
|
10153
|
+
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
10154
|
+
const gaugeArcWidth = gaugeRect.width / 6;
|
|
10155
|
+
const gaugePercentage = gaugeValue
|
|
10156
|
+
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
10157
|
+
: 0;
|
|
10158
|
+
const gaugeValuePosition = {
|
|
10159
|
+
x: boundingRect.width / 2,
|
|
10160
|
+
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
10065
10161
|
};
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
return this.chartRuntime.background;
|
|
10162
|
+
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
10163
|
+
// Scale down the font size if the gaugeRect is too small
|
|
10164
|
+
if (gaugeRect.height < 300) {
|
|
10165
|
+
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
10071
10166
|
}
|
|
10072
|
-
|
|
10073
|
-
|
|
10167
|
+
// Scale down the font size if the text is too long
|
|
10168
|
+
const maxTextWidth = gaugeRect.width / 2;
|
|
10169
|
+
const gaugeLabel = gaugeValue?.label || "-";
|
|
10170
|
+
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
10171
|
+
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
10074
10172
|
}
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10173
|
+
const minLabelPosition = {
|
|
10174
|
+
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
10175
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10176
|
+
};
|
|
10177
|
+
const maxLabelPosition = {
|
|
10178
|
+
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
10179
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10180
|
+
};
|
|
10181
|
+
const textColor = chartMutedFontColor(runtime.background);
|
|
10182
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
10183
|
+
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
10184
|
+
if (runtime.title.text) {
|
|
10185
|
+
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
10081
10186
|
}
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10187
|
+
switch (runtime.title.align) {
|
|
10188
|
+
case "right":
|
|
10189
|
+
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
10190
|
+
break;
|
|
10191
|
+
case "center":
|
|
10192
|
+
x = (boundingRect.width - titleWidth) / 2;
|
|
10193
|
+
break;
|
|
10194
|
+
case "left":
|
|
10195
|
+
default:
|
|
10196
|
+
x = CHART_PADDING$1;
|
|
10197
|
+
break;
|
|
10198
|
+
}
|
|
10199
|
+
return {
|
|
10200
|
+
width: boundingRect.width,
|
|
10201
|
+
height: boundingRect.height,
|
|
10202
|
+
title: {
|
|
10203
|
+
label: runtime.title.text ?? "",
|
|
10204
|
+
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
10205
|
+
textPosition: {
|
|
10206
|
+
x,
|
|
10207
|
+
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
10208
|
+
},
|
|
10209
|
+
color: runtime.title.color ?? textColor,
|
|
10210
|
+
bold: runtime.title.bold,
|
|
10211
|
+
italic: runtime.title.italic,
|
|
10212
|
+
},
|
|
10213
|
+
backgroundColor: runtime.background,
|
|
10214
|
+
gauge: {
|
|
10215
|
+
rect: gaugeRect,
|
|
10216
|
+
arcWidth: gaugeArcWidth,
|
|
10217
|
+
percentage: clip(gaugePercentage, 0, 1),
|
|
10218
|
+
color: getGaugeColor(runtime),
|
|
10219
|
+
},
|
|
10220
|
+
inflectionValues,
|
|
10221
|
+
gaugeValue: {
|
|
10222
|
+
label: gaugeLabel,
|
|
10223
|
+
textPosition: gaugeValuePosition,
|
|
10224
|
+
fontSize: gaugeValueFontSize,
|
|
10225
|
+
color: textColor,
|
|
10226
|
+
},
|
|
10227
|
+
minLabel: {
|
|
10228
|
+
label: runtime.minValue.label,
|
|
10229
|
+
textPosition: minLabelPosition,
|
|
10230
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10231
|
+
color: textColor,
|
|
10232
|
+
},
|
|
10233
|
+
maxLabel: {
|
|
10234
|
+
label: runtime.maxValue.label,
|
|
10235
|
+
textPosition: maxLabelPosition,
|
|
10236
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10237
|
+
color: textColor,
|
|
10238
|
+
},
|
|
10239
|
+
};
|
|
10240
|
+
}
|
|
10241
|
+
/**
|
|
10242
|
+
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
10243
|
+
* space for the title and labels.
|
|
10244
|
+
*/
|
|
10245
|
+
function getGaugeRect(boundingRect, title) {
|
|
10246
|
+
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
10247
|
+
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
10248
|
+
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
10249
|
+
let gaugeWidth;
|
|
10250
|
+
let gaugeHeight;
|
|
10251
|
+
if (drawWidth > 2 * drawHeight) {
|
|
10252
|
+
gaugeWidth = 2 * drawHeight;
|
|
10253
|
+
gaugeHeight = drawHeight;
|
|
10254
|
+
}
|
|
10255
|
+
else {
|
|
10256
|
+
gaugeWidth = drawWidth;
|
|
10257
|
+
gaugeHeight = drawWidth / 2;
|
|
10258
|
+
}
|
|
10259
|
+
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
10260
|
+
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
10261
|
+
return {
|
|
10262
|
+
x: gaugeX,
|
|
10263
|
+
y: gaugeY,
|
|
10264
|
+
width: gaugeWidth,
|
|
10265
|
+
height: gaugeHeight,
|
|
10266
|
+
};
|
|
10267
|
+
}
|
|
10268
|
+
/**
|
|
10269
|
+
* 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).
|
|
10270
|
+
*
|
|
10271
|
+
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
10272
|
+
*/
|
|
10273
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
10274
|
+
const maxValue = runtime.maxValue;
|
|
10275
|
+
const minValue = runtime.minValue;
|
|
10276
|
+
const gaugeCircleCenter = {
|
|
10277
|
+
x: gaugeRect.x + gaugeRect.width / 2,
|
|
10278
|
+
y: gaugeRect.y + gaugeRect.height,
|
|
10279
|
+
};
|
|
10280
|
+
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
10281
|
+
const inflectionValues = [];
|
|
10282
|
+
const inflectionValuesTextRects = [];
|
|
10283
|
+
for (const inflectionValue of runtime.inflectionValues) {
|
|
10284
|
+
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
10285
|
+
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
10286
|
+
const angle = Math.PI - Math.PI * percentage;
|
|
10287
|
+
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
10288
|
+
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
10289
|
+
gaugeCircleCenter.x, // center of the gauge circle
|
|
10290
|
+
gaugeCircleCenter.y, // center of the gauge circle
|
|
10291
|
+
labelWidth + 2, // width of the text + some margin
|
|
10292
|
+
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
10293
|
+
);
|
|
10294
|
+
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
10295
|
+
? GAUGE_LABELS_FONT_SIZE
|
|
10296
|
+
: 0;
|
|
10297
|
+
inflectionValuesTextRects.push(textRect);
|
|
10298
|
+
inflectionValues.push({
|
|
10299
|
+
rotation: angle,
|
|
10300
|
+
label: inflectionValue.label,
|
|
10301
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10302
|
+
color: textColor,
|
|
10303
|
+
offset,
|
|
10102
10304
|
});
|
|
10103
10305
|
}
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10306
|
+
return inflectionValues;
|
|
10307
|
+
}
|
|
10308
|
+
function getGaugeColor(runtime) {
|
|
10309
|
+
const gaugeValue = runtime.gaugeValue?.value;
|
|
10310
|
+
if (gaugeValue === undefined) {
|
|
10311
|
+
return GAUGE_BACKGROUND_COLOR;
|
|
10108
10312
|
}
|
|
10109
|
-
|
|
10110
|
-
const
|
|
10111
|
-
if (
|
|
10112
|
-
|
|
10113
|
-
if (chartData.options?.plugins?.title) {
|
|
10114
|
-
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
10115
|
-
}
|
|
10313
|
+
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
10314
|
+
const inflectionValue = runtime.inflectionValues[i];
|
|
10315
|
+
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
10316
|
+
return runtime.colors[i];
|
|
10116
10317
|
}
|
|
10117
|
-
else {
|
|
10118
|
-
|
|
10318
|
+
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
10319
|
+
return runtime.colors[i];
|
|
10119
10320
|
}
|
|
10120
|
-
this.chart.config.options = chartData.options;
|
|
10121
|
-
this.chart.update();
|
|
10122
10321
|
}
|
|
10322
|
+
return runtime.colors.at(-1);
|
|
10323
|
+
}
|
|
10324
|
+
function getSegmentsOfRectangle(rectangle) {
|
|
10325
|
+
return [
|
|
10326
|
+
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
10327
|
+
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
10328
|
+
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
10329
|
+
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
10330
|
+
];
|
|
10331
|
+
}
|
|
10332
|
+
/**
|
|
10333
|
+
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
10334
|
+
* is not handled.
|
|
10335
|
+
*/
|
|
10336
|
+
function doSegmentIntersect(segment1, segment2) {
|
|
10337
|
+
const A = segment1.start;
|
|
10338
|
+
const B = segment1.end;
|
|
10339
|
+
const C = segment2.start;
|
|
10340
|
+
const D = segment2.end;
|
|
10341
|
+
/**
|
|
10342
|
+
* Line segment intersection algorithm
|
|
10343
|
+
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
10344
|
+
*/
|
|
10345
|
+
function ccw(a, b, c) {
|
|
10346
|
+
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
10347
|
+
}
|
|
10348
|
+
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
10349
|
+
}
|
|
10350
|
+
function doRectanglesIntersect(rect1, rect2) {
|
|
10351
|
+
const segments1 = getSegmentsOfRectangle(rect1);
|
|
10352
|
+
const segments2 = getSegmentsOfRectangle(rect2);
|
|
10353
|
+
for (const segment1 of segments1) {
|
|
10354
|
+
for (const segment2 of segments2) {
|
|
10355
|
+
if (doSegmentIntersect(segment1, segment2)) {
|
|
10356
|
+
return true;
|
|
10357
|
+
}
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
return false;
|
|
10361
|
+
}
|
|
10362
|
+
/**
|
|
10363
|
+
* Get the rectangle that is tangent to a circle at a given angle.
|
|
10364
|
+
*
|
|
10365
|
+
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
10366
|
+
*/
|
|
10367
|
+
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
10368
|
+
const cos = Math.cos(angle);
|
|
10369
|
+
const sin = Math.sin(angle);
|
|
10370
|
+
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
10371
|
+
const x = cos * radius;
|
|
10372
|
+
const y = sin * radius;
|
|
10373
|
+
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
10374
|
+
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
10375
|
+
const y2 = cos * (rectWidth / 2);
|
|
10376
|
+
const bottomRight = {
|
|
10377
|
+
x: x + x2 + circleCenterX,
|
|
10378
|
+
y: circleCenterY - (y - y2),
|
|
10379
|
+
};
|
|
10380
|
+
const bottomLeft = {
|
|
10381
|
+
x: x - x2 + circleCenterX,
|
|
10382
|
+
y: circleCenterY - (y + y2),
|
|
10383
|
+
};
|
|
10384
|
+
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
10385
|
+
const xp = cos * (radius + rectHeight);
|
|
10386
|
+
const yp = sin * (radius + rectHeight);
|
|
10387
|
+
const topLeft = {
|
|
10388
|
+
x: xp - x2 + circleCenterX,
|
|
10389
|
+
y: circleCenterY - (yp + y2),
|
|
10390
|
+
};
|
|
10391
|
+
const topRight = {
|
|
10392
|
+
x: xp + x2 + circleCenterX,
|
|
10393
|
+
y: circleCenterY - (yp - y2),
|
|
10394
|
+
};
|
|
10395
|
+
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
10123
10396
|
}
|
|
10124
10397
|
|
|
10125
10398
|
/**
|
|
@@ -10701,6 +10974,155 @@ class ScorecardChartConfigBuilder {
|
|
|
10701
10974
|
}
|
|
10702
10975
|
}
|
|
10703
10976
|
|
|
10977
|
+
const CHART_COMMON_OPTIONS = {
|
|
10978
|
+
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
10979
|
+
responsive: true, // will resize when its container is resized
|
|
10980
|
+
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
10981
|
+
elements: {
|
|
10982
|
+
line: {
|
|
10983
|
+
fill: false, // do not fill the area under line charts
|
|
10984
|
+
},
|
|
10985
|
+
point: {
|
|
10986
|
+
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
10987
|
+
},
|
|
10988
|
+
},
|
|
10989
|
+
animation: false,
|
|
10990
|
+
};
|
|
10991
|
+
function truncateLabel(label) {
|
|
10992
|
+
if (!label) {
|
|
10993
|
+
return "";
|
|
10994
|
+
}
|
|
10995
|
+
if (label.length > MAX_CHAR_LABEL) {
|
|
10996
|
+
return label.substring(0, MAX_CHAR_LABEL) + "…";
|
|
10997
|
+
}
|
|
10998
|
+
return label;
|
|
10999
|
+
}
|
|
11000
|
+
function chartToImage(runtime, figure, type) {
|
|
11001
|
+
// wrap the canvas in a div with a fixed size because chart.js would
|
|
11002
|
+
// fill the whole page otherwise
|
|
11003
|
+
const div = document.createElement("div");
|
|
11004
|
+
div.style.width = `${figure.width}px`;
|
|
11005
|
+
div.style.height = `${figure.height}px`;
|
|
11006
|
+
const canvas = document.createElement("canvas");
|
|
11007
|
+
div.append(canvas);
|
|
11008
|
+
canvas.setAttribute("width", figure.width.toString());
|
|
11009
|
+
canvas.setAttribute("height", figure.height.toString());
|
|
11010
|
+
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
11011
|
+
document.body.append(div);
|
|
11012
|
+
if ("chartJsConfig" in runtime) {
|
|
11013
|
+
const config = deepCopy(runtime.chartJsConfig);
|
|
11014
|
+
config.plugins = [backgroundColorChartJSPlugin];
|
|
11015
|
+
const Chart = getChartJSConstructor();
|
|
11016
|
+
const chart = new Chart(canvas, config);
|
|
11017
|
+
const imgContent = chart.toBase64Image();
|
|
11018
|
+
chart.destroy();
|
|
11019
|
+
div.remove();
|
|
11020
|
+
return imgContent;
|
|
11021
|
+
}
|
|
11022
|
+
else if (type === "scorecard") {
|
|
11023
|
+
const design = getScorecardConfiguration(figure, runtime);
|
|
11024
|
+
drawScoreChart(design, canvas);
|
|
11025
|
+
const imgContent = canvas.toDataURL();
|
|
11026
|
+
div.remove();
|
|
11027
|
+
return imgContent;
|
|
11028
|
+
}
|
|
11029
|
+
else if (type === "gauge") {
|
|
11030
|
+
drawGaugeChart(canvas, runtime);
|
|
11031
|
+
const imgContent = canvas.toDataURL();
|
|
11032
|
+
div.remove();
|
|
11033
|
+
return imgContent;
|
|
11034
|
+
}
|
|
11035
|
+
return undefined;
|
|
11036
|
+
}
|
|
11037
|
+
/**
|
|
11038
|
+
* Custom chart.js plugin to set the background color of the canvas
|
|
11039
|
+
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
11040
|
+
*/
|
|
11041
|
+
const backgroundColorChartJSPlugin = {
|
|
11042
|
+
id: "customCanvasBackgroundColor",
|
|
11043
|
+
beforeDraw: (chart) => {
|
|
11044
|
+
const { ctx } = chart;
|
|
11045
|
+
ctx.save();
|
|
11046
|
+
ctx.globalCompositeOperation = "destination-over";
|
|
11047
|
+
ctx.fillStyle = "#ffffff";
|
|
11048
|
+
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
11049
|
+
ctx.restore();
|
|
11050
|
+
},
|
|
11051
|
+
};
|
|
11052
|
+
/** Return window.Chart, making sure all our extensions are loaded in ChartJS */
|
|
11053
|
+
function getChartJSConstructor() {
|
|
11054
|
+
if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
|
|
11055
|
+
window.Chart.register(chartShowValuesPlugin);
|
|
11056
|
+
window.Chart.register(waterfallLinesPlugin);
|
|
11057
|
+
}
|
|
11058
|
+
return window.Chart;
|
|
11059
|
+
}
|
|
11060
|
+
|
|
11061
|
+
class ChartJsComponent extends Component {
|
|
11062
|
+
static template = "o-spreadsheet-ChartJsComponent";
|
|
11063
|
+
static props = {
|
|
11064
|
+
figure: Object,
|
|
11065
|
+
};
|
|
11066
|
+
canvas = useRef("graphContainer");
|
|
11067
|
+
chart;
|
|
11068
|
+
currentRuntime;
|
|
11069
|
+
get background() {
|
|
11070
|
+
return this.chartRuntime.background;
|
|
11071
|
+
}
|
|
11072
|
+
get canvasStyle() {
|
|
11073
|
+
return `background-color: ${this.background}`;
|
|
11074
|
+
}
|
|
11075
|
+
get chartRuntime() {
|
|
11076
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
|
|
11077
|
+
if (!("chartJsConfig" in runtime)) {
|
|
11078
|
+
throw new Error("Unsupported chart runtime");
|
|
11079
|
+
}
|
|
11080
|
+
return runtime;
|
|
11081
|
+
}
|
|
11082
|
+
setup() {
|
|
11083
|
+
onMounted(() => {
|
|
11084
|
+
const runtime = this.chartRuntime;
|
|
11085
|
+
this.currentRuntime = runtime;
|
|
11086
|
+
// Note: chartJS modify the runtime in place, so it's important to give it a copy
|
|
11087
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11088
|
+
});
|
|
11089
|
+
onWillUnmount(() => this.chart?.destroy());
|
|
11090
|
+
useEffect(() => {
|
|
11091
|
+
const runtime = this.chartRuntime;
|
|
11092
|
+
if (runtime !== this.currentRuntime) {
|
|
11093
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
11094
|
+
this.chart?.destroy();
|
|
11095
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11096
|
+
}
|
|
11097
|
+
else {
|
|
11098
|
+
this.updateChartJs(deepCopy(runtime));
|
|
11099
|
+
}
|
|
11100
|
+
this.currentRuntime = runtime;
|
|
11101
|
+
}
|
|
11102
|
+
});
|
|
11103
|
+
}
|
|
11104
|
+
createChart(chartData) {
|
|
11105
|
+
const canvas = this.canvas.el;
|
|
11106
|
+
const ctx = canvas.getContext("2d");
|
|
11107
|
+
const Chart = getChartJSConstructor();
|
|
11108
|
+
this.chart = new Chart(ctx, chartData);
|
|
11109
|
+
}
|
|
11110
|
+
updateChartJs(chartRuntime) {
|
|
11111
|
+
const chartData = chartRuntime.chartJsConfig;
|
|
11112
|
+
if (chartData.data && chartData.data.datasets) {
|
|
11113
|
+
this.chart.data = chartData.data;
|
|
11114
|
+
if (chartData.options?.plugins?.title) {
|
|
11115
|
+
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
11116
|
+
}
|
|
11117
|
+
}
|
|
11118
|
+
else {
|
|
11119
|
+
this.chart.data.datasets = [];
|
|
11120
|
+
}
|
|
11121
|
+
this.chart.config.options = chartData.options;
|
|
11122
|
+
this.chart.update();
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
|
|
10704
11126
|
class ScorecardChart extends Component {
|
|
10705
11127
|
static template = "o-spreadsheet-ScorecardChart";
|
|
10706
11128
|
static props = {
|
|
@@ -22153,7 +22575,7 @@ autofillRulesRegistry
|
|
|
22153
22575
|
condition: (cell) => !cell.isFormula &&
|
|
22154
22576
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
|
|
22155
22577
|
alphaNumericValueRegExp.test(cell.content),
|
|
22156
|
-
generateRule: (cell, cells) => {
|
|
22578
|
+
generateRule: (cell, cells, direction) => {
|
|
22157
22579
|
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
22158
22580
|
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
22159
22581
|
const numberPostfixLength = cell.content.length - prefix.length;
|
|
@@ -22161,7 +22583,10 @@ autofillRulesRegistry
|
|
|
22161
22583
|
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
22162
22584
|
.filter((cell) => prefix === (cell.value ?? "").toString().match(stringPrefixRegExp)[0])
|
|
22163
22585
|
.map((cell) => parseInt((cell.value ?? "").toString().match(numberPostfixRegExp)[0]));
|
|
22164
|
-
|
|
22586
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22587
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22588
|
+
increment = -increment;
|
|
22589
|
+
}
|
|
22165
22590
|
return {
|
|
22166
22591
|
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
22167
22592
|
prefix,
|
|
@@ -22224,10 +22649,13 @@ autofillRulesRegistry
|
|
|
22224
22649
|
.add("increment_number", {
|
|
22225
22650
|
condition: (cell) => !cell.isFormula &&
|
|
22226
22651
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22227
|
-
generateRule: (cell, cells) => {
|
|
22652
|
+
generateRule: (cell, cells, direction) => {
|
|
22228
22653
|
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22229
22654
|
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22230
|
-
|
|
22655
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22656
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22657
|
+
increment = -increment;
|
|
22658
|
+
}
|
|
22231
22659
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22232
22660
|
return {
|
|
22233
22661
|
type: "INCREMENT_MODIFIER",
|
|
@@ -22271,343 +22699,6 @@ function getDateIntervals(dates) {
|
|
|
22271
22699
|
|
|
22272
22700
|
const cellPopoverRegistry = new Registry();
|
|
22273
22701
|
|
|
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
22702
|
class GaugeChartComponent extends Component {
|
|
22612
22703
|
static template = "o-spreadsheet-GaugeChartComponent";
|
|
22613
22704
|
canvas = useRef("chartContainer");
|
|
@@ -22640,81 +22731,6 @@ function toXlsxHexColor(color) {
|
|
|
22640
22731
|
return color;
|
|
22641
22732
|
}
|
|
22642
22733
|
|
|
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
22734
|
/**
|
|
22719
22735
|
* Represent a raw XML string
|
|
22720
22736
|
*/
|
|
@@ -22776,6 +22792,7 @@ const DRAWING_NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
|
22776
22792
|
const CONTENT_TYPES = {
|
|
22777
22793
|
workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
22778
22794
|
sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
|
22795
|
+
metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
|
|
22779
22796
|
sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
|
22780
22797
|
styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
|
22781
22798
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
@@ -22788,6 +22805,7 @@ const CONTENT_TYPES = {
|
|
|
22788
22805
|
const XLSX_RELATION_TYPE = {
|
|
22789
22806
|
document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
22790
22807
|
sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
|
22808
|
+
metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
|
|
22791
22809
|
sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
|
|
22792
22810
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
22793
22811
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -22797,6 +22815,7 @@ const XLSX_RELATION_TYPE = {
|
|
|
22797
22815
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22798
22816
|
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22799
22817
|
};
|
|
22818
|
+
const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
|
|
22800
22819
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22801
22820
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
22802
22821
|
/**
|
|
@@ -25362,29 +25381,34 @@ function convertPivotTableConfig(pivotTable) {
|
|
|
25362
25381
|
* In all the sheets, replace the table-only references in the formula cells with standard references.
|
|
25363
25382
|
*/
|
|
25364
25383
|
function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
25365
|
-
for (let
|
|
25366
|
-
const tables = xlsxSheets.find((s) => s.sheetName ===
|
|
25384
|
+
for (let tableSheet of convertedSheets) {
|
|
25385
|
+
const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
|
|
25367
25386
|
for (let table of tables) {
|
|
25368
25387
|
const tabRef = table.name + "[";
|
|
25369
|
-
for (let
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25388
|
+
for (let sheet of convertedSheets) {
|
|
25389
|
+
for (let xc in sheet.cells) {
|
|
25390
|
+
const cell = sheet.cells[xc];
|
|
25391
|
+
let cellContent = sheet.cells[xc];
|
|
25392
|
+
if (cell && cellContent && cellContent.startsWith("=")) {
|
|
25393
|
+
let refIndex;
|
|
25394
|
+
while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
|
|
25395
|
+
let endIndex = refIndex + tabRef.length;
|
|
25396
|
+
let openBrackets = 1;
|
|
25397
|
+
while (openBrackets > 0 && endIndex < cellContent.length) {
|
|
25398
|
+
if (cellContent[endIndex] === "[") {
|
|
25399
|
+
openBrackets++;
|
|
25400
|
+
}
|
|
25401
|
+
else if (cellContent[endIndex] === "]") {
|
|
25402
|
+
openBrackets--;
|
|
25403
|
+
}
|
|
25404
|
+
endIndex++;
|
|
25405
|
+
}
|
|
25406
|
+
let reference = cellContent.slice(refIndex + tabRef.length, endIndex - 1);
|
|
25407
|
+
const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
|
|
25408
|
+
const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
|
|
25409
|
+
cellContent =
|
|
25410
|
+
cellContent.slice(0, refIndex) + convertedRef + cellContent.slice(endIndex);
|
|
25381
25411
|
}
|
|
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
25412
|
}
|
|
25389
25413
|
sheet.cells[xc] = cellContent;
|
|
25390
25414
|
}
|
|
@@ -25393,11 +25417,17 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25393
25417
|
}
|
|
25394
25418
|
}
|
|
25395
25419
|
/**
|
|
25396
|
-
* Convert table-specific references in formulas into standard references.
|
|
25420
|
+
* Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
|
|
25421
|
+
* and of keywords determining the rows of the table to reference.
|
|
25397
25422
|
*
|
|
25398
25423
|
* A reference in a table can have the form (only the part between brackets should be given to this function):
|
|
25399
25424
|
* - tableName[colName] : reference to the whole column "colName"
|
|
25425
|
+
* - tableName[#keyword] : reference to the whatever row the keyword refers to
|
|
25400
25426
|
* - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
|
|
25427
|
+
* - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
|
|
25428
|
+
* - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
|
|
25429
|
+
* - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
|
|
25430
|
+
*
|
|
25401
25431
|
*
|
|
25402
25432
|
* The available keywords are :
|
|
25403
25433
|
* - #All : all the column (including totals)
|
|
@@ -25405,58 +25435,109 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25405
25435
|
* - #Headers : only the header of the column
|
|
25406
25436
|
* - #Totals : only the totals of the column
|
|
25407
25437
|
* - #This Row : only the element in the same row as the cell
|
|
25438
|
+
*
|
|
25439
|
+
* Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
|
|
25408
25440
|
*/
|
|
25409
|
-
function convertTableReference(expr, table, cellXc) {
|
|
25410
|
-
|
|
25441
|
+
function convertTableReference(sheetPrefix, expr, table, cellXc) {
|
|
25442
|
+
// TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
|
|
25443
|
+
// contain # or , characters. But that's probably an edge case that we can ignore for now.
|
|
25444
|
+
const parts = expr.split(",").map((part) => part.trim());
|
|
25411
25445
|
const tableZone = toZone(table.ref);
|
|
25412
|
-
const
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
25417
|
-
|
|
25418
|
-
|
|
25419
|
-
|
|
25420
|
-
|
|
25421
|
-
|
|
25422
|
-
|
|
25446
|
+
const colIndexes = [];
|
|
25447
|
+
const rowIndexes = [];
|
|
25448
|
+
const foundKeywords = [];
|
|
25449
|
+
for (const part of parts) {
|
|
25450
|
+
if (removeBrackets(part).startsWith("#")) {
|
|
25451
|
+
const keyWord = removeBrackets(part);
|
|
25452
|
+
foundKeywords.push(keyWord);
|
|
25453
|
+
switch (keyWord) {
|
|
25454
|
+
case "#All":
|
|
25455
|
+
rowIndexes.push(tableZone.top, tableZone.bottom);
|
|
25456
|
+
break;
|
|
25457
|
+
case "#Data":
|
|
25458
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25459
|
+
const bottom = table.totalsRowCount
|
|
25460
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25461
|
+
: tableZone.bottom;
|
|
25462
|
+
rowIndexes.push(top, bottom);
|
|
25463
|
+
break;
|
|
25464
|
+
case "#This Row":
|
|
25465
|
+
rowIndexes.push(toCartesian(cellXc).row);
|
|
25466
|
+
break;
|
|
25467
|
+
case "#Headers":
|
|
25468
|
+
if (!table.headerRowCount) {
|
|
25469
|
+
return CellErrorType.InvalidReference;
|
|
25470
|
+
}
|
|
25471
|
+
rowIndexes.push(tableZone.top);
|
|
25472
|
+
break;
|
|
25473
|
+
case "#Totals":
|
|
25474
|
+
if (!table.totalsRowCount) {
|
|
25475
|
+
return CellErrorType.InvalidReference;
|
|
25476
|
+
}
|
|
25477
|
+
rowIndexes.push(tableZone.bottom);
|
|
25478
|
+
break;
|
|
25479
|
+
}
|
|
25423
25480
|
}
|
|
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;
|
|
25481
|
+
else {
|
|
25482
|
+
const columns = part
|
|
25483
|
+
.split(":")
|
|
25484
|
+
.map((part) => part.trim())
|
|
25485
|
+
.map(removeBrackets);
|
|
25486
|
+
if (colIndexes.length) {
|
|
25487
|
+
return CellErrorType.InvalidReference;
|
|
25488
|
+
}
|
|
25489
|
+
const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
|
|
25490
|
+
if (colRelativeIndex === -1) {
|
|
25491
|
+
return CellErrorType.InvalidReference;
|
|
25492
|
+
}
|
|
25493
|
+
colIndexes.push(colRelativeIndex + tableZone.left);
|
|
25494
|
+
if (columns[1]) {
|
|
25495
|
+
const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
|
|
25496
|
+
if (colRelativeIndex2 === -1) {
|
|
25497
|
+
return CellErrorType.InvalidReference;
|
|
25449
25498
|
}
|
|
25450
|
-
|
|
25499
|
+
colIndexes.push(colRelativeIndex2 + tableZone.left);
|
|
25500
|
+
}
|
|
25451
25501
|
}
|
|
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
25502
|
}
|
|
25456
|
-
if (!
|
|
25503
|
+
if (!areKeywordsCompatible(foundKeywords)) {
|
|
25457
25504
|
return CellErrorType.InvalidReference;
|
|
25458
25505
|
}
|
|
25459
|
-
|
|
25506
|
+
if (rowIndexes.length === 0) {
|
|
25507
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25508
|
+
const bottom = table.totalsRowCount
|
|
25509
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25510
|
+
: tableZone.bottom;
|
|
25511
|
+
rowIndexes.push(top, bottom);
|
|
25512
|
+
}
|
|
25513
|
+
if (colIndexes.length === 0) {
|
|
25514
|
+
colIndexes.push(tableZone.left, tableZone.right);
|
|
25515
|
+
}
|
|
25516
|
+
const refZone = {
|
|
25517
|
+
top: Math.min(...rowIndexes),
|
|
25518
|
+
left: Math.min(...colIndexes),
|
|
25519
|
+
bottom: Math.max(...rowIndexes),
|
|
25520
|
+
right: Math.max(...colIndexes),
|
|
25521
|
+
};
|
|
25522
|
+
return sheetPrefix + zoneToXc(refZone);
|
|
25523
|
+
}
|
|
25524
|
+
function removeBrackets(str) {
|
|
25525
|
+
return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
|
|
25526
|
+
}
|
|
25527
|
+
function areKeywordsCompatible(keywords) {
|
|
25528
|
+
if (keywords.length < 2) {
|
|
25529
|
+
return true;
|
|
25530
|
+
}
|
|
25531
|
+
else if (keywords.length > 2) {
|
|
25532
|
+
return false;
|
|
25533
|
+
}
|
|
25534
|
+
else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
|
|
25535
|
+
return true;
|
|
25536
|
+
}
|
|
25537
|
+
else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
|
|
25538
|
+
return true;
|
|
25539
|
+
}
|
|
25540
|
+
return false;
|
|
25460
25541
|
}
|
|
25461
25542
|
|
|
25462
25543
|
// -------------------------------------
|
|
@@ -28624,11 +28705,12 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
|
|
|
28624
28705
|
}
|
|
28625
28706
|
let missingTimeAdapterAlreadyWarned = false;
|
|
28626
28707
|
function isLuxonTimeAdapterInstalled() {
|
|
28627
|
-
|
|
28708
|
+
const Chart = getChartJSConstructor();
|
|
28709
|
+
if (!Chart) {
|
|
28628
28710
|
return false;
|
|
28629
28711
|
}
|
|
28630
28712
|
// @ts-ignore
|
|
28631
|
-
const adapter = new
|
|
28713
|
+
const adapter = new Chart._adapters._date({});
|
|
28632
28714
|
const isInstalled = adapter._id === "luxon";
|
|
28633
28715
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
28634
28716
|
missingTimeAdapterAlreadyWarned = true;
|
|
@@ -32325,10 +32407,6 @@ class Popover extends Component {
|
|
|
32325
32407
|
this.currentDisplayValue = newDisplay;
|
|
32326
32408
|
if (!anchor)
|
|
32327
32409
|
return;
|
|
32328
|
-
el.style.top = "";
|
|
32329
|
-
el.style.left = "";
|
|
32330
|
-
el.style["max-height"] = "";
|
|
32331
|
-
el.style["max-width"] = "";
|
|
32332
32410
|
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
32333
32411
|
let elDims = {
|
|
32334
32412
|
width: el.getBoundingClientRect().width,
|
|
@@ -33866,6 +33944,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
|
|
|
33866
33944
|
drawScoreChart: drawScoreChart,
|
|
33867
33945
|
formatChartDatasetValue: formatChartDatasetValue,
|
|
33868
33946
|
formatTickValue: formatTickValue,
|
|
33947
|
+
getChartJSConstructor: getChartJSConstructor,
|
|
33869
33948
|
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
33870
33949
|
getDefinedAxis: getDefinedAxis,
|
|
33871
33950
|
getPieColors: getPieColors,
|
|
@@ -37749,6 +37828,9 @@ class GenericChartConfigPanel extends Component {
|
|
|
37749
37828
|
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
|
|
37750
37829
|
dataSets: this.dataSeriesRanges,
|
|
37751
37830
|
});
|
|
37831
|
+
if (this.state.datasetDispatchResult.isSuccessful) {
|
|
37832
|
+
this.dataSeriesRanges = this.env.model.getters.getChartDefinition(this.props.figureId).dataSets;
|
|
37833
|
+
}
|
|
37752
37834
|
}
|
|
37753
37835
|
getDataSeriesRanges() {
|
|
37754
37836
|
return this.dataSeriesRanges;
|
|
@@ -40388,8 +40470,7 @@ css /* scss */ `
|
|
|
40388
40470
|
}
|
|
40389
40471
|
|
|
40390
40472
|
.o-composer-assistant {
|
|
40391
|
-
|
|
40392
|
-
margin: 1px 4px;
|
|
40473
|
+
margin-top: 1px;
|
|
40393
40474
|
|
|
40394
40475
|
.o-semi-bold {
|
|
40395
40476
|
/* FIXME: to remove in favor of Bootstrap
|
|
@@ -40440,10 +40521,11 @@ class Composer extends Component {
|
|
|
40440
40521
|
});
|
|
40441
40522
|
compositionActive = false;
|
|
40442
40523
|
spreadsheetRect = useSpreadsheetRect();
|
|
40443
|
-
get
|
|
40524
|
+
get assistantStyleProperties() {
|
|
40444
40525
|
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
40445
40526
|
const assistantStyle = {};
|
|
40446
|
-
|
|
40527
|
+
const minWidth = Math.min(this.props.rect?.width || Infinity, ASSISTANT_WIDTH);
|
|
40528
|
+
assistantStyle["min-width"] = `${minWidth}px`;
|
|
40447
40529
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
40448
40530
|
const proposalsHaveDescription = proposals?.some((proposal) => proposal.description);
|
|
40449
40531
|
if (this.functionDescriptionState.showDescription || proposalsHaveDescription) {
|
|
@@ -40467,13 +40549,29 @@ class Composer extends Component {
|
|
|
40467
40549
|
}
|
|
40468
40550
|
}
|
|
40469
40551
|
else {
|
|
40470
|
-
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
40552
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom - 1}px`; // -1: margin
|
|
40471
40553
|
if (composerRect.left + ASSISTANT_WIDTH + SCROLLBAR_WIDTH + CLOSE_ICON_RADIUS >
|
|
40472
40554
|
this.spreadsheetRect.width) {
|
|
40473
40555
|
assistantStyle.right = `${CLOSE_ICON_RADIUS}px`;
|
|
40474
40556
|
}
|
|
40475
40557
|
}
|
|
40476
|
-
return
|
|
40558
|
+
return assistantStyle;
|
|
40559
|
+
}
|
|
40560
|
+
get assistantStyle() {
|
|
40561
|
+
const allProperties = this.assistantStyleProperties;
|
|
40562
|
+
return cssPropertiesToCss({
|
|
40563
|
+
"max-height": allProperties["max-height"],
|
|
40564
|
+
width: allProperties["width"],
|
|
40565
|
+
"min-width": allProperties["min-width"],
|
|
40566
|
+
});
|
|
40567
|
+
}
|
|
40568
|
+
get assistantContainerStyle() {
|
|
40569
|
+
const allProperties = this.assistantStyleProperties;
|
|
40570
|
+
return cssPropertiesToCss({
|
|
40571
|
+
top: allProperties["top"],
|
|
40572
|
+
right: allProperties["right"],
|
|
40573
|
+
transform: allProperties["transform"],
|
|
40574
|
+
});
|
|
40477
40575
|
}
|
|
40478
40576
|
// we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
|
|
40479
40577
|
shouldProcessInputEvents = false;
|
|
@@ -46410,9 +46508,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46410
46508
|
pivot: this.draft,
|
|
46411
46509
|
});
|
|
46412
46510
|
this.draft = null;
|
|
46413
|
-
if (!this.alreadyNotified &&
|
|
46414
|
-
!this.isDynamicPivotInViewport() &&
|
|
46415
|
-
this.isStaticPivotInViewport()) {
|
|
46511
|
+
if (!this.alreadyNotified && this.isUpdatedPivotVisibleInViewportOnlyAsStaticPivot()) {
|
|
46416
46512
|
const formulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46417
46513
|
const pivotExample = `=PIVOT(${formulaId})`;
|
|
46418
46514
|
this.alreadyNotified = true;
|
|
@@ -46468,29 +46564,33 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46468
46564
|
this.applyUpdate();
|
|
46469
46565
|
}
|
|
46470
46566
|
}
|
|
46471
|
-
|
|
46472
|
-
|
|
46473
|
-
|
|
46474
|
-
|
|
46475
|
-
|
|
46476
|
-
|
|
46477
|
-
|
|
46478
|
-
}
|
|
46479
|
-
}
|
|
46480
|
-
}
|
|
46481
|
-
return false;
|
|
46482
|
-
}
|
|
46483
|
-
isStaticPivotInViewport() {
|
|
46567
|
+
/**
|
|
46568
|
+
* @returns true if the updated pivot is visible in the viewport only as a
|
|
46569
|
+
* static pivot and not as a dynamic pivot
|
|
46570
|
+
*/
|
|
46571
|
+
isUpdatedPivotVisibleInViewportOnlyAsStaticPivot() {
|
|
46572
|
+
let staticPivotCount = 0;
|
|
46573
|
+
const updatedPivotFormulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46484
46574
|
for (const position of this.getters.getVisibleCellPositions()) {
|
|
46485
46575
|
const cell = this.getters.getCell(position);
|
|
46486
46576
|
if (cell?.isFormula) {
|
|
46487
46577
|
const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
46488
|
-
|
|
46489
|
-
|
|
46578
|
+
const pivotFormulaId = pivotFunction?.args[0]?.value;
|
|
46579
|
+
if (pivotFunction && updatedPivotFormulaId === pivotFormulaId.toString()) {
|
|
46580
|
+
if (pivotFunction.functionName === "PIVOT") {
|
|
46581
|
+
// if we have at least one dynamic pivot visible inserted the viewport
|
|
46582
|
+
// we return false
|
|
46583
|
+
return false;
|
|
46584
|
+
}
|
|
46585
|
+
else {
|
|
46586
|
+
staticPivotCount++;
|
|
46587
|
+
}
|
|
46490
46588
|
}
|
|
46491
46589
|
}
|
|
46492
46590
|
}
|
|
46493
|
-
return
|
|
46591
|
+
// we return true if there are only static pivots visible inserted the viewport,
|
|
46592
|
+
// otherwise false
|
|
46593
|
+
return staticPivotCount > 0;
|
|
46494
46594
|
}
|
|
46495
46595
|
addDefaultDateTimeGranularity(fields, definition) {
|
|
46496
46596
|
const { columns, rows } = definition;
|
|
@@ -60330,6 +60430,7 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60330
60430
|
exportForExcel(data) {
|
|
60331
60431
|
for (const sheet of data.sheets) {
|
|
60332
60432
|
sheet.cellValues = {};
|
|
60433
|
+
sheet.formulaSpillRanges = {};
|
|
60333
60434
|
}
|
|
60334
60435
|
for (const position of this.evaluator.getEvaluatedPositions()) {
|
|
60335
60436
|
const evaluatedCell = this.evaluator.getEvaluatedCell(position);
|
|
@@ -60341,8 +60442,9 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60341
60442
|
const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
|
|
60342
60443
|
const formulaCell = this.getCorrespondingFormulaCell(position);
|
|
60343
60444
|
if (formulaCell) {
|
|
60445
|
+
const cell = this.getters.getCell(position);
|
|
60344
60446
|
isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
|
|
60345
|
-
isFormula = isExported;
|
|
60447
|
+
isFormula = isExported && cell?.content === formulaCell.content;
|
|
60346
60448
|
// If the cell contains a non-exported formula and that is evaluates to
|
|
60347
60449
|
// nothing* ,we don't export it.
|
|
60348
60450
|
// * non-falsy value are relevant and so are 0 and FALSE, which only leaves
|
|
@@ -60365,7 +60467,11 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
60365
60467
|
content = !isExported ? newContent : exportedCellData;
|
|
60366
60468
|
}
|
|
60367
60469
|
exportedSheetData.cells[xc] = content;
|
|
60368
|
-
exportedSheetData.cellValues[xc] = value;
|
|
60470
|
+
exportedSheetData.cellValues[xc] = evaluatedCell.type !== "error" ? value : undefined;
|
|
60471
|
+
const spillZone = this.getSpreadZone(position);
|
|
60472
|
+
if (spillZone) {
|
|
60473
|
+
exportedSheetData.formulaSpillRanges[xc] = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
|
|
60474
|
+
}
|
|
60369
60475
|
}
|
|
60370
60476
|
}
|
|
60371
60477
|
/**
|
|
@@ -62572,7 +62678,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
62572
62678
|
getRule(cell, cells) {
|
|
62573
62679
|
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
62574
62680
|
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
62575
|
-
return rule && rule.generateRule(cell, cells);
|
|
62681
|
+
return rule && this.direction && rule.generateRule(cell, cells, this.direction);
|
|
62576
62682
|
}
|
|
62577
62683
|
/**
|
|
62578
62684
|
* Create the generator to be able to autofill the next cells.
|
|
@@ -73102,7 +73208,7 @@ function numberRef(reference) {
|
|
|
73102
73208
|
`;
|
|
73103
73209
|
}
|
|
73104
73210
|
|
|
73105
|
-
function addFormula(formula, value) {
|
|
73211
|
+
function addFormula(formula, value, formulaSpillRange) {
|
|
73106
73212
|
if (!formula) {
|
|
73107
73213
|
return { attrs: [], node: escapeXml `` };
|
|
73108
73214
|
}
|
|
@@ -73110,10 +73216,17 @@ function addFormula(formula, value) {
|
|
|
73110
73216
|
if (type === undefined) {
|
|
73111
73217
|
return { attrs: [], node: escapeXml `` };
|
|
73112
73218
|
}
|
|
73113
|
-
const attrs = [
|
|
73219
|
+
const attrs = [
|
|
73220
|
+
["cm", "1"],
|
|
73221
|
+
["t", type],
|
|
73222
|
+
];
|
|
73114
73223
|
const XlsxFormula = adaptFormulaToExcel(formula);
|
|
73115
73224
|
const exportedValue = adaptFormulaValueToExcel(value);
|
|
73116
|
-
|
|
73225
|
+
// We treat all formulas as array formulas (a simple formula
|
|
73226
|
+
// is an array formula that spills on only one cell) to avoid
|
|
73227
|
+
// trying to detect spilling sub-formulas which is not a trivial task.
|
|
73228
|
+
let node;
|
|
73229
|
+
node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
|
|
73117
73230
|
return { attrs, node };
|
|
73118
73231
|
}
|
|
73119
73232
|
function addContent(content, sharedStrings, forceString = false) {
|
|
@@ -74103,7 +74216,7 @@ function addRows(construct, data, sheet) {
|
|
|
74103
74216
|
let cellNode = escapeXml ``;
|
|
74104
74217
|
// Either formula or static value inside the cell
|
|
74105
74218
|
if (content?.startsWith("=") && value !== undefined) {
|
|
74106
|
-
const res = addFormula(content, value);
|
|
74219
|
+
const res = addFormula(content, value, sheet.formulaSpillRanges[xc] ?? xc);
|
|
74107
74220
|
if (!res) {
|
|
74108
74221
|
continue;
|
|
74109
74222
|
}
|
|
@@ -74389,6 +74502,30 @@ function createWorksheets(data, construct) {
|
|
|
74389
74502
|
`;
|
|
74390
74503
|
files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
|
|
74391
74504
|
}
|
|
74505
|
+
const sheetMetadataXml = escapeXml /*xml*/ `
|
|
74506
|
+
<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
|
|
74507
|
+
<metadataTypes count="1">
|
|
74508
|
+
<metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
|
|
74509
|
+
pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
|
|
74510
|
+
clearComments="1" assign="1" coerce="1" cellMeta="1" />
|
|
74511
|
+
</metadataTypes>
|
|
74512
|
+
<futureMetadata name="XLDAPR" count="1">
|
|
74513
|
+
<bk>
|
|
74514
|
+
<extLst>
|
|
74515
|
+
<ext uri="{${ARRAY_FORMULA_URI}}">
|
|
74516
|
+
<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
|
|
74517
|
+
</ext>
|
|
74518
|
+
</extLst>
|
|
74519
|
+
</bk>
|
|
74520
|
+
</futureMetadata>
|
|
74521
|
+
<cellMetadata count="1">
|
|
74522
|
+
<bk>
|
|
74523
|
+
<rc t="1" v="0" />
|
|
74524
|
+
</bk>
|
|
74525
|
+
</cellMetadata>
|
|
74526
|
+
</metadata>
|
|
74527
|
+
`;
|
|
74528
|
+
files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
|
|
74392
74529
|
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74393
74530
|
type: XLSX_RELATION_TYPE.sharedStrings,
|
|
74394
74531
|
target: "sharedStrings.xml",
|
|
@@ -74397,6 +74534,10 @@ function createWorksheets(data, construct) {
|
|
|
74397
74534
|
type: XLSX_RELATION_TYPE.styles,
|
|
74398
74535
|
target: "styles.xml",
|
|
74399
74536
|
});
|
|
74537
|
+
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74538
|
+
type: XLSX_RELATION_TYPE.metadata,
|
|
74539
|
+
target: "metadata.xml",
|
|
74540
|
+
});
|
|
74400
74541
|
return files;
|
|
74401
74542
|
}
|
|
74402
74543
|
/**
|
|
@@ -75281,6 +75422,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
|
|
|
75281
75422
|
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
75423
|
|
|
75283
75424
|
|
|
75284
|
-
__info__.version = "18.1.
|
|
75285
|
-
__info__.date = "2025-
|
|
75286
|
-
__info__.hash = "
|
|
75425
|
+
__info__.version = "18.1.10";
|
|
75426
|
+
__info__.date = "2025-03-07T10:34:41.861Z";
|
|
75427
|
+
__info__.hash = "31e4526";
|