@odoo/o-spreadsheet 19.5.0-alpha.1 → 19.5.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o_spreadsheet.cjs +1047 -846
- package/dist/o_spreadsheet.css +22 -5
- package/dist/o_spreadsheet.esm.js +1045 -844
- package/dist/o_spreadsheet.iife.js +1047 -846
- package/dist/o_spreadsheet.min.iife.js +266 -266
- package/dist/o_spreadsheet.xml +59 -26
- package/dist/types/clipboard_handlers/default_clipboard.d.ts +1 -1
- package/dist/types/components/figures/chart/scorecard/chart_scorecard.d.ts +2 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +2 -2
- package/dist/types/components/side_panel/criterion_form/calendar_button/calendar_button.d.ts +13 -0
- package/dist/types/components/side_panel/criterion_form/date_criterion/date_criterion.d.ts +2 -0
- package/dist/types/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.d.ts +3 -0
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/helpers/figures/charts/scorecard_chart_config_builder.d.ts +2 -0
- package/dist/types/helpers/internal_viewport.d.ts +1 -0
- package/dist/types/helpers/misc.d.ts +1 -0
- package/dist/types/helpers/text_helper.d.ts +6 -1
- package/dist/types/helpers/viewport_collection.d.ts +1 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/types/chart/radar_chart.d.ts +1 -1
- package/dist/types/types/chart/scorecard_chart.d.ts +2 -0
- package/dist/types/types/misc.d.ts +4 -0
- 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 19.5.0-alpha.
|
|
6
|
-
* @date 2026-07-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.5.0-alpha.3
|
|
6
|
+
* @date 2026-07-14T10:17:06.949Z
|
|
7
|
+
* @hash c029184
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as owl from "@odoo/owl";
|
|
@@ -562,6 +562,7 @@ const DEFAULT_NUMBER_STYLE = {
|
|
|
562
562
|
};
|
|
563
563
|
const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
|
|
564
564
|
const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
|
|
565
|
+
const DEFAULT_TEXT_HIGHLIGHT_PERCENT = .25;
|
|
565
566
|
const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
|
|
566
567
|
const DEFAULT_FONT = "'Roboto', arial, 'Liberation Sans'";
|
|
567
568
|
const DEFAULT_BORDER_DESC = {
|
|
@@ -1246,6 +1247,12 @@ function defaultDict(def) {
|
|
|
1246
1247
|
}
|
|
1247
1248
|
};
|
|
1248
1249
|
}
|
|
1250
|
+
function repeat(array, times) {
|
|
1251
|
+
const len = array.length;
|
|
1252
|
+
const result = new Array(len * times);
|
|
1253
|
+
for (let t = 0; t < times; t++) for (let i = 0; i < len; i++) result[t * len + i] = array[i];
|
|
1254
|
+
return result;
|
|
1255
|
+
}
|
|
1249
1256
|
|
|
1250
1257
|
//#endregion
|
|
1251
1258
|
//#region src/helpers/coordinates.ts
|
|
@@ -8405,534 +8412,6 @@ function addOrigin(cell, origin) {
|
|
|
8405
8412
|
return cell;
|
|
8406
8413
|
}
|
|
8407
8414
|
|
|
8408
|
-
//#endregion
|
|
8409
|
-
//#region src/helpers/text_helper.ts
|
|
8410
|
-
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
8411
|
-
return numberOfLines * (textLineHeight + 4) - 4;
|
|
8412
|
-
}
|
|
8413
|
-
function getCanvas(width = 100, height = 100) {
|
|
8414
|
-
return new OffscreenCanvas(width, height).getContext("2d");
|
|
8415
|
-
}
|
|
8416
|
-
/**
|
|
8417
|
-
* Get the default height of the cell given its style.
|
|
8418
|
-
*/
|
|
8419
|
-
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
8420
|
-
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
8421
|
-
let content = "";
|
|
8422
|
-
try {
|
|
8423
|
-
if (!cell.isFormula) {
|
|
8424
|
-
const localeFormat = {
|
|
8425
|
-
format: cell.format,
|
|
8426
|
-
locale
|
|
8427
|
-
};
|
|
8428
|
-
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
8429
|
-
}
|
|
8430
|
-
} catch {
|
|
8431
|
-
content = CellErrorType.GenericError;
|
|
8432
|
-
}
|
|
8433
|
-
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
8434
|
-
}
|
|
8435
|
-
function getCellContentHeight(ctx, content, style, colSize) {
|
|
8436
|
-
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
8437
|
-
}
|
|
8438
|
-
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
8439
|
-
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
8440
|
-
}
|
|
8441
|
-
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
8442
|
-
if (!textLines.length) return {
|
|
8443
|
-
width: 0,
|
|
8444
|
-
height: 0
|
|
8445
|
-
};
|
|
8446
|
-
const font = computeTextFont(style, fontUnit);
|
|
8447
|
-
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
8448
|
-
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
8449
|
-
const width = Math.max(...sizes.map((size) => size.width));
|
|
8450
|
-
if (!style.rotation) return {
|
|
8451
|
-
height,
|
|
8452
|
-
width
|
|
8453
|
-
};
|
|
8454
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8455
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8456
|
-
return {
|
|
8457
|
-
width: width * cos + height * sin,
|
|
8458
|
-
height: sin * width + cos * height
|
|
8459
|
-
};
|
|
8460
|
-
}
|
|
8461
|
-
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
8462
|
-
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
8463
|
-
}
|
|
8464
|
-
function computeCachedTextWidth(context, text, font, rotation) {
|
|
8465
|
-
const size = computeCachedTextDimension(context, text, font);
|
|
8466
|
-
if (!rotation) return size.width;
|
|
8467
|
-
const cos = Math.abs(Math.cos(rotation));
|
|
8468
|
-
const sin = Math.abs(Math.sin(rotation));
|
|
8469
|
-
return size.width * cos + size.height * sin;
|
|
8470
|
-
}
|
|
8471
|
-
const textDimensionsCache = {};
|
|
8472
|
-
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
8473
|
-
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
8474
|
-
if (!style.rotation) return size;
|
|
8475
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8476
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8477
|
-
return {
|
|
8478
|
-
width: size.width * cos + size.height * sin,
|
|
8479
|
-
height: size.height * cos + size.width * sin
|
|
8480
|
-
};
|
|
8481
|
-
}
|
|
8482
|
-
function computeCachedTextDimension(context, text, font) {
|
|
8483
|
-
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
8484
|
-
if (textDimensionsCache[font][text] === void 0) {
|
|
8485
|
-
context.save();
|
|
8486
|
-
context.font = font;
|
|
8487
|
-
const measure = context.measureText(text);
|
|
8488
|
-
context.restore();
|
|
8489
|
-
const width = measure.width;
|
|
8490
|
-
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8491
|
-
textDimensionsCache[font][text] = {
|
|
8492
|
-
width,
|
|
8493
|
-
height
|
|
8494
|
-
};
|
|
8495
|
-
}
|
|
8496
|
-
return textDimensionsCache[font][text];
|
|
8497
|
-
}
|
|
8498
|
-
function fontSizeInPixels(fontSize) {
|
|
8499
|
-
return Math.round(fontSize * 96 / 72);
|
|
8500
|
-
}
|
|
8501
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
8502
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
8503
|
-
}
|
|
8504
|
-
function computeTextFontSizeInPixels(style) {
|
|
8505
|
-
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
8506
|
-
}
|
|
8507
|
-
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
8508
|
-
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
8509
|
-
const splitWord = [];
|
|
8510
|
-
let wordPart = "";
|
|
8511
|
-
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
8512
|
-
splitWord.push(wordPart);
|
|
8513
|
-
wordPart = l;
|
|
8514
|
-
} else wordPart += l;
|
|
8515
|
-
splitWord.push(wordPart);
|
|
8516
|
-
return splitWord;
|
|
8517
|
-
}
|
|
8518
|
-
/**
|
|
8519
|
-
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
8520
|
-
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
8521
|
-
*/
|
|
8522
|
-
function splitTextToWidth(ctx, text, style, width) {
|
|
8523
|
-
if (!style) style = {};
|
|
8524
|
-
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
8525
|
-
const brokenText = [];
|
|
8526
|
-
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
8527
|
-
for (const line of lines) {
|
|
8528
|
-
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
8529
|
-
if (!width) {
|
|
8530
|
-
brokenText.push(line);
|
|
8531
|
-
continue;
|
|
8532
|
-
}
|
|
8533
|
-
let textLine = "";
|
|
8534
|
-
let availableWidth = width;
|
|
8535
|
-
for (const word of words) {
|
|
8536
|
-
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
8537
|
-
const lastPart = splitWord.pop();
|
|
8538
|
-
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
8539
|
-
if (splitWord.length) {
|
|
8540
|
-
if (textLine !== "") {
|
|
8541
|
-
brokenText.push(textLine);
|
|
8542
|
-
textLine = "";
|
|
8543
|
-
availableWidth = width;
|
|
8544
|
-
}
|
|
8545
|
-
splitWord.forEach((wordPart) => {
|
|
8546
|
-
brokenText.push(wordPart);
|
|
8547
|
-
});
|
|
8548
|
-
textLine = lastPart;
|
|
8549
|
-
availableWidth = width - lastPartWidth;
|
|
8550
|
-
} else {
|
|
8551
|
-
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
8552
|
-
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
8553
|
-
if (wordWidth <= availableWidth) {
|
|
8554
|
-
textLine += _word;
|
|
8555
|
-
availableWidth -= wordWidth;
|
|
8556
|
-
} else {
|
|
8557
|
-
brokenText.push(textLine);
|
|
8558
|
-
textLine = lastPart;
|
|
8559
|
-
availableWidth = width - lastPartWidth;
|
|
8560
|
-
}
|
|
8561
|
-
}
|
|
8562
|
-
}
|
|
8563
|
-
if (textLine !== "") brokenText.push(textLine);
|
|
8564
|
-
}
|
|
8565
|
-
return brokenText;
|
|
8566
|
-
}
|
|
8567
|
-
/**
|
|
8568
|
-
* Return the font size that makes the width of a text match the given line width.
|
|
8569
|
-
* Minimum font size is 1.
|
|
8570
|
-
*
|
|
8571
|
-
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
8572
|
-
*/
|
|
8573
|
-
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
8574
|
-
let minFontSize = 1;
|
|
8575
|
-
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
8576
|
-
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
8577
|
-
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
8578
|
-
let currentTextWidth = getTextWidth(fontSize);
|
|
8579
|
-
let iterations = 0;
|
|
8580
|
-
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
8581
|
-
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
8582
|
-
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
8583
|
-
fontSize = (minFontSize + maxFontSize) / 2;
|
|
8584
|
-
currentTextWidth = getTextWidth(fontSize);
|
|
8585
|
-
iterations++;
|
|
8586
|
-
}
|
|
8587
|
-
return fontSize;
|
|
8588
|
-
}
|
|
8589
|
-
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
8590
|
-
function toTrimmedLowerCase(str) {
|
|
8591
|
-
return str ? str.toLowerCase().trim() : "";
|
|
8592
|
-
}
|
|
8593
|
-
/**
|
|
8594
|
-
* Extract the fontSize from a context font string
|
|
8595
|
-
* @param font The (context) font string to parse
|
|
8596
|
-
* @returns The fontSize in pixels
|
|
8597
|
-
*/
|
|
8598
|
-
const pxRegex = /([0-9\.]*)px/;
|
|
8599
|
-
function getContextFontSize(font) {
|
|
8600
|
-
return Number(font.match(pxRegex)?.[1]);
|
|
8601
|
-
}
|
|
8602
|
-
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
8603
|
-
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8604
|
-
if (width <= maxWidth) return text;
|
|
8605
|
-
const ellipsis = "…";
|
|
8606
|
-
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
8607
|
-
if (width <= ellipsisWidth) return text;
|
|
8608
|
-
let len = text.length;
|
|
8609
|
-
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
8610
|
-
text = text.substring(0, len);
|
|
8611
|
-
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8612
|
-
}
|
|
8613
|
-
return text + ellipsis;
|
|
8614
|
-
}
|
|
8615
|
-
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10) {
|
|
8616
|
-
context.fillText(text, position.x, position.y);
|
|
8617
|
-
if (!underline && !strikethrough) return;
|
|
8618
|
-
const measure = context.measureText(text);
|
|
8619
|
-
const textWidth = measure.width;
|
|
8620
|
-
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
8621
|
-
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8622
|
-
let { x, y } = position;
|
|
8623
|
-
let strikeY = y, underlineY = y;
|
|
8624
|
-
switch (context.textAlign) {
|
|
8625
|
-
case "center":
|
|
8626
|
-
x -= textWidth / 2;
|
|
8627
|
-
break;
|
|
8628
|
-
case "right":
|
|
8629
|
-
x -= textWidth;
|
|
8630
|
-
break;
|
|
8631
|
-
}
|
|
8632
|
-
switch (context.textBaseline) {
|
|
8633
|
-
case "top":
|
|
8634
|
-
underlineY += boxHeight - 2 * strokeWidth;
|
|
8635
|
-
strikeY += boxHeight / 2 - strokeWidth;
|
|
8636
|
-
break;
|
|
8637
|
-
case "middle":
|
|
8638
|
-
underlineY += boxHeight / 2 - strokeWidth;
|
|
8639
|
-
break;
|
|
8640
|
-
case "alphabetic":
|
|
8641
|
-
underlineY += 2 * strokeWidth;
|
|
8642
|
-
strikeY -= 3 * strokeWidth;
|
|
8643
|
-
break;
|
|
8644
|
-
case "bottom":
|
|
8645
|
-
underlineY = y;
|
|
8646
|
-
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
8647
|
-
break;
|
|
8648
|
-
}
|
|
8649
|
-
if (underline) {
|
|
8650
|
-
context.lineWidth = strokeWidth;
|
|
8651
|
-
context.strokeStyle = context.fillStyle;
|
|
8652
|
-
context.beginPath();
|
|
8653
|
-
context.moveTo(x, underlineY);
|
|
8654
|
-
context.lineTo(x + textWidth, underlineY);
|
|
8655
|
-
context.stroke();
|
|
8656
|
-
}
|
|
8657
|
-
if (strikethrough) {
|
|
8658
|
-
context.lineWidth = strokeWidth;
|
|
8659
|
-
context.strokeStyle = context.fillStyle;
|
|
8660
|
-
context.beginPath();
|
|
8661
|
-
context.moveTo(x, strikeY);
|
|
8662
|
-
context.lineTo(x + textWidth, strikeY);
|
|
8663
|
-
context.stroke();
|
|
8664
|
-
}
|
|
8665
|
-
}
|
|
8666
|
-
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
8667
|
-
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
8668
|
-
const ellipsis = "...";
|
|
8669
|
-
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
8670
|
-
if (ellipsisWidth >= width) return "";
|
|
8671
|
-
let lowerBoundLen = 1;
|
|
8672
|
-
let upperBoundLen = text.length;
|
|
8673
|
-
let currentWidth;
|
|
8674
|
-
while (lowerBoundLen <= upperBoundLen) {
|
|
8675
|
-
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
8676
|
-
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
8677
|
-
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
8678
|
-
else lowerBoundLen = currentLen + 1;
|
|
8679
|
-
}
|
|
8680
|
-
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
8681
|
-
return slicedText ? slicedText + ellipsis : "";
|
|
8682
|
-
}
|
|
8683
|
-
/**
|
|
8684
|
-
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
8685
|
-
* with to original's text vertical and horizontal alignment.
|
|
8686
|
-
*/
|
|
8687
|
-
function computeRotationPosition(rect, style) {
|
|
8688
|
-
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
8689
|
-
let { x, y } = rect;
|
|
8690
|
-
const cos = Math.cos(-style.rotation);
|
|
8691
|
-
const sin = Math.sin(-style.rotation);
|
|
8692
|
-
const width = rect.textWidth - 2 * 4;
|
|
8693
|
-
const height = rect.textHeight;
|
|
8694
|
-
const center = style.align === "center";
|
|
8695
|
-
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
8696
|
-
const sh = sin * height;
|
|
8697
|
-
const sw = Math.abs(sin * width);
|
|
8698
|
-
const ch = cos * height;
|
|
8699
|
-
if (style.verticalAlign === "top") if (center) {
|
|
8700
|
-
y += sw / 2;
|
|
8701
|
-
x -= sh / 2;
|
|
8702
|
-
} else if (rotateTowardCellCenter) x -= sh;
|
|
8703
|
-
else y += sw;
|
|
8704
|
-
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
8705
|
-
y += height - ch;
|
|
8706
|
-
if (center) {
|
|
8707
|
-
y -= sw / 2;
|
|
8708
|
-
x -= sh / 2;
|
|
8709
|
-
} else if (rotateTowardCellCenter) {
|
|
8710
|
-
x -= sh;
|
|
8711
|
-
y -= sw;
|
|
8712
|
-
}
|
|
8713
|
-
} else if (center) {
|
|
8714
|
-
x -= sh / 2;
|
|
8715
|
-
y -= height / 2;
|
|
8716
|
-
if (rotateTowardCellCenter) y += sh;
|
|
8717
|
-
else y -= sh;
|
|
8718
|
-
} else if (rotateTowardCellCenter) {
|
|
8719
|
-
x -= sh;
|
|
8720
|
-
y -= sw / 2;
|
|
8721
|
-
} else y += sw / 2 + ch / 4;
|
|
8722
|
-
return {
|
|
8723
|
-
x: cos * x - sin * y,
|
|
8724
|
-
y: cos * y + sin * x
|
|
8725
|
-
};
|
|
8726
|
-
}
|
|
8727
|
-
|
|
8728
|
-
//#endregion
|
|
8729
|
-
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
8730
|
-
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
8731
|
-
const chartColorScalePlugin = {
|
|
8732
|
-
id: "chartColorScalePlugin",
|
|
8733
|
-
afterDatasetsDraw(chart, args, options) {
|
|
8734
|
-
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
8735
|
-
const ctx = chart.ctx;
|
|
8736
|
-
ctx.save();
|
|
8737
|
-
ctx.textAlign = "center";
|
|
8738
|
-
ctx.textBaseline = "middle";
|
|
8739
|
-
ctx.miterLimit = 1;
|
|
8740
|
-
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
8741
|
-
const gradientWidth = 10;
|
|
8742
|
-
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
8743
|
-
const gradientY = chart.chartArea.top;
|
|
8744
|
-
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
8745
|
-
const step = 1 / (options.colorScale.length - 1);
|
|
8746
|
-
options.colorScale.forEach((color, index) => {
|
|
8747
|
-
gradient.addColorStop(index * step, color);
|
|
8748
|
-
});
|
|
8749
|
-
ctx.fillStyle = gradient;
|
|
8750
|
-
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
8751
|
-
ctx.fillStyle = options.fontColor ?? "black";
|
|
8752
|
-
ctx.font = getDefaultContextFont(12);
|
|
8753
|
-
ctx.textAlign = "left";
|
|
8754
|
-
let minValue = Math.round(options.minValue * 100) / 100;
|
|
8755
|
-
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
8756
|
-
if (options.minValue === options.maxValue) {
|
|
8757
|
-
minValue -= 1;
|
|
8758
|
-
maxValue += 1;
|
|
8759
|
-
}
|
|
8760
|
-
const formattedMaxValue = humanizeNumber({
|
|
8761
|
-
value: maxValue,
|
|
8762
|
-
format: void 0
|
|
8763
|
-
}, options.locale);
|
|
8764
|
-
const formattedMinValue = humanizeNumber({
|
|
8765
|
-
value: minValue,
|
|
8766
|
-
format: void 0
|
|
8767
|
-
}, options.locale);
|
|
8768
|
-
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
8769
|
-
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
8770
|
-
ctx.restore();
|
|
8771
|
-
}
|
|
8772
|
-
};
|
|
8773
|
-
|
|
8774
|
-
//#endregion
|
|
8775
|
-
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
8776
|
-
function getFunnelChartController() {
|
|
8777
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8778
|
-
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
8779
|
-
static id = "funnel";
|
|
8780
|
-
static defaults = {
|
|
8781
|
-
...globalThis.Chart?.BarController.defaults,
|
|
8782
|
-
dataElementType: "funnel",
|
|
8783
|
-
animation: { duration: (ctx) => {
|
|
8784
|
-
if (ctx.type !== "data") return 1e3;
|
|
8785
|
-
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
8786
|
-
} }
|
|
8787
|
-
};
|
|
8788
|
-
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
8789
|
-
updateElements(rects, start, count, mode) {
|
|
8790
|
-
super.updateElements(rects, start, count, mode);
|
|
8791
|
-
for (let i = start; i < start + count; i++) {
|
|
8792
|
-
const rect = rects[i];
|
|
8793
|
-
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
8794
|
-
}
|
|
8795
|
-
}
|
|
8796
|
-
};
|
|
8797
|
-
}
|
|
8798
|
-
function getFunnelChartElement() {
|
|
8799
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8800
|
-
/**
|
|
8801
|
-
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
8802
|
-
* `width`, and the bottom is of width `nextElementWidth`.
|
|
8803
|
-
*/
|
|
8804
|
-
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
8805
|
-
static id = "funnel";
|
|
8806
|
-
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
8807
|
-
draw(ctx) {
|
|
8808
|
-
ctx.save();
|
|
8809
|
-
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
8810
|
-
"x",
|
|
8811
|
-
"y",
|
|
8812
|
-
"width",
|
|
8813
|
-
"height",
|
|
8814
|
-
"nextElement",
|
|
8815
|
-
"base",
|
|
8816
|
-
"options"
|
|
8817
|
-
]);
|
|
8818
|
-
const width = getElementWidth(this);
|
|
8819
|
-
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
8820
|
-
const startX = Math.min(x, base);
|
|
8821
|
-
const startY = y - height / 2;
|
|
8822
|
-
ctx.fillStyle = options.backgroundColor;
|
|
8823
|
-
ctx.beginPath();
|
|
8824
|
-
ctx.moveTo(startX, startY);
|
|
8825
|
-
ctx.lineTo(startX + width, startY);
|
|
8826
|
-
ctx.lineTo(startX + width - offset, startY + height);
|
|
8827
|
-
ctx.lineTo(startX + offset, startY + height);
|
|
8828
|
-
ctx.closePath();
|
|
8829
|
-
ctx.fill();
|
|
8830
|
-
if (options.borderWidth) {
|
|
8831
|
-
ctx.strokeStyle = options.borderColor;
|
|
8832
|
-
ctx.lineWidth = options.borderWidth;
|
|
8833
|
-
ctx.stroke();
|
|
8834
|
-
}
|
|
8835
|
-
ctx.restore();
|
|
8836
|
-
}
|
|
8837
|
-
/** Check if the mouse is inside the trapezoid */
|
|
8838
|
-
inRange(mouseX, mouseY) {
|
|
8839
|
-
const { x, y, height, nextElement, base } = this.getProps([
|
|
8840
|
-
"x",
|
|
8841
|
-
"y",
|
|
8842
|
-
"width",
|
|
8843
|
-
"height",
|
|
8844
|
-
"nextElement",
|
|
8845
|
-
"base",
|
|
8846
|
-
"options"
|
|
8847
|
-
]);
|
|
8848
|
-
const width = getElementWidth(this);
|
|
8849
|
-
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
8850
|
-
const startX = Math.min(x, base);
|
|
8851
|
-
const startY = y - height / 2;
|
|
8852
|
-
if (mouseY < startY || mouseY > startY + height) return false;
|
|
8853
|
-
const offset = (width - nextElementWidth) / 2;
|
|
8854
|
-
const left = startX + offset * (mouseY - startY) / height;
|
|
8855
|
-
const right = startX + width - offset * (mouseY - startY) / height;
|
|
8856
|
-
if (mouseX < left || mouseX > right) return false;
|
|
8857
|
-
return true;
|
|
8858
|
-
}
|
|
8859
|
-
};
|
|
8860
|
-
}
|
|
8861
|
-
/**
|
|
8862
|
-
* Get an element width.
|
|
8863
|
-
*
|
|
8864
|
-
* The property width is undefined during animations, we need to compute it manually.
|
|
8865
|
-
*/
|
|
8866
|
-
function getElementWidth(element) {
|
|
8867
|
-
const { x, base } = element.getProps(["x", "base"]);
|
|
8868
|
-
return Math.max(x, base) - Math.min(x, base);
|
|
8869
|
-
}
|
|
8870
|
-
/**
|
|
8871
|
-
* Position the tooltip inside the trapezoid.
|
|
8872
|
-
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
8873
|
-
*/
|
|
8874
|
-
const funnelTooltipPositioner = function(elements) {
|
|
8875
|
-
if (!elements.length) return {
|
|
8876
|
-
x: 0,
|
|
8877
|
-
y: 0
|
|
8878
|
-
};
|
|
8879
|
-
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
8880
|
-
"x",
|
|
8881
|
-
"y",
|
|
8882
|
-
"width",
|
|
8883
|
-
"height",
|
|
8884
|
-
"base"
|
|
8885
|
-
]);
|
|
8886
|
-
const startX = Math.min(x, base);
|
|
8887
|
-
const startY = y - height / 2;
|
|
8888
|
-
return {
|
|
8889
|
-
x: startX + width * 2 / 3,
|
|
8890
|
-
y: startY + height / 2
|
|
8891
|
-
};
|
|
8892
|
-
};
|
|
8893
|
-
|
|
8894
|
-
//#endregion
|
|
8895
|
-
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
8896
|
-
const chartMinorGridPlugin = {
|
|
8897
|
-
id: "o-spreadsheet-minor-gridlines",
|
|
8898
|
-
beforeDatasetsDraw(chart) {
|
|
8899
|
-
const ctx = chart.ctx;
|
|
8900
|
-
const chartArea = chart.chartArea;
|
|
8901
|
-
if (!chartArea) return;
|
|
8902
|
-
for (const scaleId in chart.scales) {
|
|
8903
|
-
const scale = chart.scales[scaleId];
|
|
8904
|
-
const options = scale.options;
|
|
8905
|
-
const minor = options?.grid?.minor;
|
|
8906
|
-
if (!minor?.display) continue;
|
|
8907
|
-
const showMajorGrid = options?.grid?.display;
|
|
8908
|
-
const ticks = scale.ticks;
|
|
8909
|
-
if (!ticks || ticks.length < 2) continue;
|
|
8910
|
-
ctx.save();
|
|
8911
|
-
ctx.lineWidth = 1;
|
|
8912
|
-
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
8913
|
-
for (let i = 0; i < ticks.length - 1; i++) {
|
|
8914
|
-
const start = scale.getPixelForTick(i);
|
|
8915
|
-
const end = scale.getPixelForTick(i + 1);
|
|
8916
|
-
if (!isFinite(start) || !isFinite(end)) continue;
|
|
8917
|
-
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
8918
|
-
const ratio = j / 4;
|
|
8919
|
-
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
8920
|
-
ctx.beginPath();
|
|
8921
|
-
if (scale.isHorizontal()) {
|
|
8922
|
-
ctx.moveTo(position, chartArea.top);
|
|
8923
|
-
ctx.lineTo(position, chartArea.bottom);
|
|
8924
|
-
} else {
|
|
8925
|
-
ctx.moveTo(chartArea.left, position);
|
|
8926
|
-
ctx.lineTo(chartArea.right, position);
|
|
8927
|
-
}
|
|
8928
|
-
ctx.stroke();
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8931
|
-
ctx.restore();
|
|
8932
|
-
}
|
|
8933
|
-
}
|
|
8934
|
-
};
|
|
8935
|
-
|
|
8936
8415
|
//#endregion
|
|
8937
8416
|
//#region src/helpers/color.ts
|
|
8938
8417
|
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
|
|
@@ -9526,113 +9005,646 @@ var AlternatingColorGenerator = class extends ColorGenerator {
|
|
|
9526
9005
|
super(paletteSize, preferredColors);
|
|
9527
9006
|
this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
9528
9007
|
}
|
|
9529
|
-
};
|
|
9530
|
-
var AlternatingColorMap = class {
|
|
9531
|
-
availableColors;
|
|
9532
|
-
colors = {};
|
|
9533
|
-
constructor(paletteSize = 12) {
|
|
9534
|
-
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9008
|
+
};
|
|
9009
|
+
var AlternatingColorMap = class {
|
|
9010
|
+
availableColors;
|
|
9011
|
+
colors = {};
|
|
9012
|
+
constructor(paletteSize = 12) {
|
|
9013
|
+
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9014
|
+
}
|
|
9015
|
+
get(id) {
|
|
9016
|
+
if (!this.colors[id]) this.colors[id] = this.availableColors.next();
|
|
9017
|
+
return this.colors[id];
|
|
9018
|
+
}
|
|
9019
|
+
};
|
|
9020
|
+
const COLORSCHEMES = {
|
|
9021
|
+
greys: [
|
|
9022
|
+
"#ffffff",
|
|
9023
|
+
"#808080",
|
|
9024
|
+
"#000000"
|
|
9025
|
+
],
|
|
9026
|
+
blues: [
|
|
9027
|
+
"#f7fbff",
|
|
9028
|
+
"#6aaed6",
|
|
9029
|
+
"#08306b"
|
|
9030
|
+
],
|
|
9031
|
+
reds: [
|
|
9032
|
+
"#fff5f0",
|
|
9033
|
+
"#fb694a",
|
|
9034
|
+
"#67000d"
|
|
9035
|
+
],
|
|
9036
|
+
greens: [
|
|
9037
|
+
"#f7fcf5",
|
|
9038
|
+
"#73c476",
|
|
9039
|
+
"#00441b"
|
|
9040
|
+
],
|
|
9041
|
+
oranges: [
|
|
9042
|
+
"#fff5eb",
|
|
9043
|
+
"#fd8c3b",
|
|
9044
|
+
"#7f2704"
|
|
9045
|
+
],
|
|
9046
|
+
purples: [
|
|
9047
|
+
"#fcfbfd",
|
|
9048
|
+
"#9e9ac8",
|
|
9049
|
+
"#3f007d"
|
|
9050
|
+
],
|
|
9051
|
+
viridis: [
|
|
9052
|
+
"#440154",
|
|
9053
|
+
"#21918c",
|
|
9054
|
+
"#fde725"
|
|
9055
|
+
],
|
|
9056
|
+
cividis: [
|
|
9057
|
+
"#00224e",
|
|
9058
|
+
"#7d7c78",
|
|
9059
|
+
"#fee838"
|
|
9060
|
+
],
|
|
9061
|
+
rainbow: [
|
|
9062
|
+
"#B41DB4",
|
|
9063
|
+
"#FFFF00",
|
|
9064
|
+
"#00FFFF"
|
|
9065
|
+
]
|
|
9066
|
+
};
|
|
9067
|
+
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9068
|
+
/**
|
|
9069
|
+
* Returns a function that maps a value to a color using a color scale defined by the given
|
|
9070
|
+
* color/threshold values pairs.
|
|
9071
|
+
*/
|
|
9072
|
+
function getColorScale(colorScalePoints) {
|
|
9073
|
+
if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
|
|
9074
|
+
const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
|
|
9075
|
+
const thresholds = [];
|
|
9076
|
+
for (let i = 1; i < sortedColorScalePoints.length; i++) {
|
|
9077
|
+
const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
|
|
9078
|
+
const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
|
|
9079
|
+
const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
|
|
9080
|
+
const maxColor = colorToNumber(sortedColorScalePoints[i].color);
|
|
9081
|
+
thresholds.push({
|
|
9082
|
+
min: sortedColorScalePoints[i - 1].value,
|
|
9083
|
+
max: sortedColorScalePoints[i].value,
|
|
9084
|
+
minColor,
|
|
9085
|
+
maxColor,
|
|
9086
|
+
minColorAlpha,
|
|
9087
|
+
maxColorAlpha,
|
|
9088
|
+
colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
|
|
9089
|
+
});
|
|
9090
|
+
}
|
|
9091
|
+
return (value) => {
|
|
9092
|
+
if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
|
|
9093
|
+
for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
|
|
9094
|
+
return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
|
|
9095
|
+
};
|
|
9096
|
+
}
|
|
9097
|
+
function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
|
|
9098
|
+
const deltaValue = maxValue - minValue;
|
|
9099
|
+
const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
|
|
9100
|
+
const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
|
|
9101
|
+
const deltaColorB = minColor % 256 - maxColor % 256;
|
|
9102
|
+
return [
|
|
9103
|
+
deltaColorR / deltaValue,
|
|
9104
|
+
deltaColorG / deltaValue,
|
|
9105
|
+
deltaColorB / deltaValue
|
|
9106
|
+
];
|
|
9107
|
+
}
|
|
9108
|
+
function colorCell(value, minValue, minColor, colorDiffUnit) {
|
|
9109
|
+
const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
|
|
9110
|
+
const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
|
|
9111
|
+
const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
|
|
9112
|
+
const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
|
|
9113
|
+
return r << 16 | g << 8 | b;
|
|
9114
|
+
}
|
|
9115
|
+
|
|
9116
|
+
//#endregion
|
|
9117
|
+
//#region src/helpers/text_helper.ts
|
|
9118
|
+
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
9119
|
+
return numberOfLines * (textLineHeight + 4) - 4;
|
|
9120
|
+
}
|
|
9121
|
+
function getCanvas(width = 100, height = 100) {
|
|
9122
|
+
return new OffscreenCanvas(width, height).getContext("2d");
|
|
9123
|
+
}
|
|
9124
|
+
/**
|
|
9125
|
+
* Get the default height of the cell given its style.
|
|
9126
|
+
*/
|
|
9127
|
+
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
9128
|
+
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
9129
|
+
let content = "";
|
|
9130
|
+
try {
|
|
9131
|
+
if (!cell.isFormula) {
|
|
9132
|
+
const localeFormat = {
|
|
9133
|
+
format: cell.format,
|
|
9134
|
+
locale
|
|
9135
|
+
};
|
|
9136
|
+
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
9137
|
+
}
|
|
9138
|
+
} catch {
|
|
9139
|
+
content = CellErrorType.GenericError;
|
|
9140
|
+
}
|
|
9141
|
+
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
9142
|
+
}
|
|
9143
|
+
function getCellContentHeight(ctx, content, style, colSize) {
|
|
9144
|
+
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
9145
|
+
}
|
|
9146
|
+
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
9147
|
+
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
9148
|
+
}
|
|
9149
|
+
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
9150
|
+
if (!textLines.length) return {
|
|
9151
|
+
width: 0,
|
|
9152
|
+
height: 0
|
|
9153
|
+
};
|
|
9154
|
+
const font = computeTextFont(style, fontUnit);
|
|
9155
|
+
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
9156
|
+
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
9157
|
+
const width = Math.max(...sizes.map((size) => size.width));
|
|
9158
|
+
if (!style.rotation) return {
|
|
9159
|
+
height,
|
|
9160
|
+
width
|
|
9161
|
+
};
|
|
9162
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9163
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9164
|
+
return {
|
|
9165
|
+
width: width * cos + height * sin,
|
|
9166
|
+
height: sin * width + cos * height
|
|
9167
|
+
};
|
|
9168
|
+
}
|
|
9169
|
+
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
9170
|
+
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
9171
|
+
}
|
|
9172
|
+
function computeCachedTextWidth(context, text, font, rotation) {
|
|
9173
|
+
const size = computeCachedTextDimension(context, text, font);
|
|
9174
|
+
if (!rotation) return size.width;
|
|
9175
|
+
const cos = Math.abs(Math.cos(rotation));
|
|
9176
|
+
const sin = Math.abs(Math.sin(rotation));
|
|
9177
|
+
return size.width * cos + size.height * sin;
|
|
9178
|
+
}
|
|
9179
|
+
const textDimensionsCache = {};
|
|
9180
|
+
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
9181
|
+
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
9182
|
+
if (!style.rotation) return size;
|
|
9183
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9184
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9185
|
+
return {
|
|
9186
|
+
width: size.width * cos + size.height * sin,
|
|
9187
|
+
height: size.height * cos + size.width * sin
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
function computeCachedTextDimension(context, text, font) {
|
|
9191
|
+
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
9192
|
+
if (textDimensionsCache[font][text] === void 0) {
|
|
9193
|
+
context.save();
|
|
9194
|
+
context.font = font;
|
|
9195
|
+
const measure = context.measureText(text);
|
|
9196
|
+
context.restore();
|
|
9197
|
+
const width = measure.width;
|
|
9198
|
+
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9199
|
+
textDimensionsCache[font][text] = {
|
|
9200
|
+
width,
|
|
9201
|
+
height
|
|
9202
|
+
};
|
|
9203
|
+
}
|
|
9204
|
+
return textDimensionsCache[font][text];
|
|
9205
|
+
}
|
|
9206
|
+
function fontSizeInPixels(fontSize) {
|
|
9207
|
+
return Math.round(fontSize * 96 / 72);
|
|
9208
|
+
}
|
|
9209
|
+
function computeTextFont(style, fontUnit = "pt") {
|
|
9210
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9211
|
+
}
|
|
9212
|
+
function computeTextFontSizeInPixels(style) {
|
|
9213
|
+
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
9214
|
+
}
|
|
9215
|
+
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
9216
|
+
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
9217
|
+
const splitWord = [];
|
|
9218
|
+
let wordPart = "";
|
|
9219
|
+
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
9220
|
+
splitWord.push(wordPart);
|
|
9221
|
+
wordPart = l;
|
|
9222
|
+
} else wordPart += l;
|
|
9223
|
+
splitWord.push(wordPart);
|
|
9224
|
+
return splitWord;
|
|
9225
|
+
}
|
|
9226
|
+
/**
|
|
9227
|
+
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
9228
|
+
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
9229
|
+
*/
|
|
9230
|
+
function splitTextToWidth(ctx, text, style, width) {
|
|
9231
|
+
if (!style) style = {};
|
|
9232
|
+
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
9233
|
+
const brokenText = [];
|
|
9234
|
+
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
9235
|
+
for (const line of lines) {
|
|
9236
|
+
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
9237
|
+
if (!width) {
|
|
9238
|
+
brokenText.push(line);
|
|
9239
|
+
continue;
|
|
9240
|
+
}
|
|
9241
|
+
let textLine = "";
|
|
9242
|
+
let availableWidth = width;
|
|
9243
|
+
for (const word of words) {
|
|
9244
|
+
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
9245
|
+
const lastPart = splitWord.pop();
|
|
9246
|
+
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
9247
|
+
if (splitWord.length) {
|
|
9248
|
+
if (textLine !== "") {
|
|
9249
|
+
brokenText.push(textLine);
|
|
9250
|
+
textLine = "";
|
|
9251
|
+
availableWidth = width;
|
|
9252
|
+
}
|
|
9253
|
+
splitWord.forEach((wordPart) => {
|
|
9254
|
+
brokenText.push(wordPart);
|
|
9255
|
+
});
|
|
9256
|
+
textLine = lastPart;
|
|
9257
|
+
availableWidth = width - lastPartWidth;
|
|
9258
|
+
} else {
|
|
9259
|
+
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
9260
|
+
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
9261
|
+
if (wordWidth <= availableWidth) {
|
|
9262
|
+
textLine += _word;
|
|
9263
|
+
availableWidth -= wordWidth;
|
|
9264
|
+
} else {
|
|
9265
|
+
brokenText.push(textLine);
|
|
9266
|
+
textLine = lastPart;
|
|
9267
|
+
availableWidth = width - lastPartWidth;
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
if (textLine !== "") brokenText.push(textLine);
|
|
9272
|
+
}
|
|
9273
|
+
return brokenText;
|
|
9274
|
+
}
|
|
9275
|
+
/**
|
|
9276
|
+
* Return the font size that makes the width of a text match the given line width.
|
|
9277
|
+
* Minimum font size is 1.
|
|
9278
|
+
*
|
|
9279
|
+
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
9280
|
+
*/
|
|
9281
|
+
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
9282
|
+
let minFontSize = 1;
|
|
9283
|
+
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
9284
|
+
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
9285
|
+
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
9286
|
+
let currentTextWidth = getTextWidth(fontSize);
|
|
9287
|
+
let iterations = 0;
|
|
9288
|
+
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
9289
|
+
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
9290
|
+
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
9291
|
+
fontSize = (minFontSize + maxFontSize) / 2;
|
|
9292
|
+
currentTextWidth = getTextWidth(fontSize);
|
|
9293
|
+
iterations++;
|
|
9294
|
+
}
|
|
9295
|
+
return fontSize;
|
|
9296
|
+
}
|
|
9297
|
+
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
9298
|
+
function toTrimmedLowerCase(str) {
|
|
9299
|
+
return str ? str.toLowerCase().trim() : "";
|
|
9300
|
+
}
|
|
9301
|
+
/**
|
|
9302
|
+
* Extract the fontSize from a context font string
|
|
9303
|
+
* @param font The (context) font string to parse
|
|
9304
|
+
* @returns The fontSize in pixels
|
|
9305
|
+
*/
|
|
9306
|
+
const pxRegex = /([0-9\.]*)px/;
|
|
9307
|
+
function getContextFontSize(font) {
|
|
9308
|
+
return Number(font.match(pxRegex)?.[1]);
|
|
9309
|
+
}
|
|
9310
|
+
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
9311
|
+
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9312
|
+
if (width <= maxWidth) return text;
|
|
9313
|
+
const ellipsis = "…";
|
|
9314
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
9315
|
+
if (width <= ellipsisWidth) return text;
|
|
9316
|
+
let len = text.length;
|
|
9317
|
+
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
9318
|
+
text = text.substring(0, len);
|
|
9319
|
+
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9320
|
+
}
|
|
9321
|
+
return text + ellipsis;
|
|
9322
|
+
}
|
|
9323
|
+
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
|
|
9324
|
+
if (highlightText) {
|
|
9325
|
+
context.save();
|
|
9326
|
+
context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
|
|
9327
|
+
}
|
|
9328
|
+
context.fillText(text, position.x, position.y);
|
|
9329
|
+
if (!underline && !strikethrough) return;
|
|
9330
|
+
const measure = context.measureText(text);
|
|
9331
|
+
const textWidth = measure.width;
|
|
9332
|
+
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
9333
|
+
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9334
|
+
let { x, y } = position;
|
|
9335
|
+
let strikeY = y, underlineY = y;
|
|
9336
|
+
switch (context.textAlign) {
|
|
9337
|
+
case "center":
|
|
9338
|
+
x -= textWidth / 2;
|
|
9339
|
+
break;
|
|
9340
|
+
case "right":
|
|
9341
|
+
x -= textWidth;
|
|
9342
|
+
break;
|
|
9343
|
+
}
|
|
9344
|
+
switch (context.textBaseline) {
|
|
9345
|
+
case "top":
|
|
9346
|
+
underlineY += boxHeight - 2 * strokeWidth;
|
|
9347
|
+
strikeY += boxHeight / 2 - strokeWidth;
|
|
9348
|
+
break;
|
|
9349
|
+
case "middle":
|
|
9350
|
+
underlineY += boxHeight / 2 - strokeWidth;
|
|
9351
|
+
break;
|
|
9352
|
+
case "alphabetic":
|
|
9353
|
+
underlineY += 2 * strokeWidth;
|
|
9354
|
+
strikeY -= 3 * strokeWidth;
|
|
9355
|
+
break;
|
|
9356
|
+
case "bottom":
|
|
9357
|
+
underlineY = y;
|
|
9358
|
+
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
9359
|
+
break;
|
|
9360
|
+
}
|
|
9361
|
+
if (underline) {
|
|
9362
|
+
context.lineWidth = strokeWidth;
|
|
9363
|
+
context.strokeStyle = context.fillStyle;
|
|
9364
|
+
context.beginPath();
|
|
9365
|
+
context.moveTo(x, underlineY);
|
|
9366
|
+
context.lineTo(x + textWidth, underlineY);
|
|
9367
|
+
context.stroke();
|
|
9535
9368
|
}
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9369
|
+
if (strikethrough) {
|
|
9370
|
+
context.lineWidth = strokeWidth;
|
|
9371
|
+
context.strokeStyle = context.fillStyle;
|
|
9372
|
+
context.beginPath();
|
|
9373
|
+
context.moveTo(x, strikeY);
|
|
9374
|
+
context.lineTo(x + textWidth, strikeY);
|
|
9375
|
+
context.stroke();
|
|
9539
9376
|
}
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
"#73c476",
|
|
9560
|
-
"#00441b"
|
|
9561
|
-
],
|
|
9562
|
-
oranges: [
|
|
9563
|
-
"#fff5eb",
|
|
9564
|
-
"#fd8c3b",
|
|
9565
|
-
"#7f2704"
|
|
9566
|
-
],
|
|
9567
|
-
purples: [
|
|
9568
|
-
"#fcfbfd",
|
|
9569
|
-
"#9e9ac8",
|
|
9570
|
-
"#3f007d"
|
|
9571
|
-
],
|
|
9572
|
-
viridis: [
|
|
9573
|
-
"#440154",
|
|
9574
|
-
"#21918c",
|
|
9575
|
-
"#fde725"
|
|
9576
|
-
],
|
|
9577
|
-
cividis: [
|
|
9578
|
-
"#00224e",
|
|
9579
|
-
"#7d7c78",
|
|
9580
|
-
"#fee838"
|
|
9581
|
-
],
|
|
9582
|
-
rainbow: [
|
|
9583
|
-
"#B41DB4",
|
|
9584
|
-
"#FFFF00",
|
|
9585
|
-
"#00FFFF"
|
|
9586
|
-
]
|
|
9587
|
-
};
|
|
9588
|
-
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9377
|
+
if (highlightText) context.restore();
|
|
9378
|
+
}
|
|
9379
|
+
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
9380
|
+
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
9381
|
+
const ellipsis = "...";
|
|
9382
|
+
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
9383
|
+
if (ellipsisWidth >= width) return "";
|
|
9384
|
+
let lowerBoundLen = 1;
|
|
9385
|
+
let upperBoundLen = text.length;
|
|
9386
|
+
let currentWidth;
|
|
9387
|
+
while (lowerBoundLen <= upperBoundLen) {
|
|
9388
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
9389
|
+
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
9390
|
+
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
9391
|
+
else lowerBoundLen = currentLen + 1;
|
|
9392
|
+
}
|
|
9393
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
9394
|
+
return slicedText ? slicedText + ellipsis : "";
|
|
9395
|
+
}
|
|
9589
9396
|
/**
|
|
9590
|
-
*
|
|
9591
|
-
*
|
|
9397
|
+
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
9398
|
+
* with to original's text vertical and horizontal alignment.
|
|
9592
9399
|
*/
|
|
9593
|
-
function
|
|
9594
|
-
if (
|
|
9595
|
-
|
|
9596
|
-
const
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9400
|
+
function computeRotationPosition(rect, style) {
|
|
9401
|
+
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
9402
|
+
let { x, y } = rect;
|
|
9403
|
+
const cos = Math.cos(-style.rotation);
|
|
9404
|
+
const sin = Math.sin(-style.rotation);
|
|
9405
|
+
const width = rect.textWidth - 2 * 4;
|
|
9406
|
+
const height = rect.textHeight;
|
|
9407
|
+
const center = style.align === "center";
|
|
9408
|
+
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
9409
|
+
const sh = sin * height;
|
|
9410
|
+
const sw = Math.abs(sin * width);
|
|
9411
|
+
const ch = cos * height;
|
|
9412
|
+
if (style.verticalAlign === "top") if (center) {
|
|
9413
|
+
y += sw / 2;
|
|
9414
|
+
x -= sh / 2;
|
|
9415
|
+
} else if (rotateTowardCellCenter) x -= sh;
|
|
9416
|
+
else y += sw;
|
|
9417
|
+
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
9418
|
+
y += height - ch;
|
|
9419
|
+
if (center) {
|
|
9420
|
+
y -= sw / 2;
|
|
9421
|
+
x -= sh / 2;
|
|
9422
|
+
} else if (rotateTowardCellCenter) {
|
|
9423
|
+
x -= sh;
|
|
9424
|
+
y -= sw;
|
|
9425
|
+
}
|
|
9426
|
+
} else if (center) {
|
|
9427
|
+
x -= sh / 2;
|
|
9428
|
+
y -= height / 2;
|
|
9429
|
+
if (rotateTowardCellCenter) y += sh;
|
|
9430
|
+
else y -= sh;
|
|
9431
|
+
} else if (rotateTowardCellCenter) {
|
|
9432
|
+
x -= sh;
|
|
9433
|
+
y -= sw / 2;
|
|
9434
|
+
} else y += sw / 2 + ch / 4;
|
|
9435
|
+
return {
|
|
9436
|
+
x: cos * x - sin * y,
|
|
9437
|
+
y: cos * y + sin * x
|
|
9438
|
+
};
|
|
9439
|
+
}
|
|
9440
|
+
|
|
9441
|
+
//#endregion
|
|
9442
|
+
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
9443
|
+
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
9444
|
+
const chartColorScalePlugin = {
|
|
9445
|
+
id: "chartColorScalePlugin",
|
|
9446
|
+
afterDatasetsDraw(chart, args, options) {
|
|
9447
|
+
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
9448
|
+
const ctx = chart.ctx;
|
|
9449
|
+
ctx.save();
|
|
9450
|
+
ctx.textAlign = "center";
|
|
9451
|
+
ctx.textBaseline = "middle";
|
|
9452
|
+
ctx.miterLimit = 1;
|
|
9453
|
+
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
9454
|
+
const gradientWidth = 10;
|
|
9455
|
+
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
9456
|
+
const gradientY = chart.chartArea.top;
|
|
9457
|
+
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
9458
|
+
const step = 1 / (options.colorScale.length - 1);
|
|
9459
|
+
options.colorScale.forEach((color, index) => {
|
|
9460
|
+
gradient.addColorStop(index * step, color);
|
|
9610
9461
|
});
|
|
9462
|
+
ctx.fillStyle = gradient;
|
|
9463
|
+
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
9464
|
+
ctx.fillStyle = options.fontColor ?? "black";
|
|
9465
|
+
ctx.font = getDefaultContextFont(12);
|
|
9466
|
+
ctx.textAlign = "left";
|
|
9467
|
+
let minValue = Math.round(options.minValue * 100) / 100;
|
|
9468
|
+
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
9469
|
+
if (options.minValue === options.maxValue) {
|
|
9470
|
+
minValue -= 1;
|
|
9471
|
+
maxValue += 1;
|
|
9472
|
+
}
|
|
9473
|
+
const formattedMaxValue = humanizeNumber({
|
|
9474
|
+
value: maxValue,
|
|
9475
|
+
format: void 0
|
|
9476
|
+
}, options.locale);
|
|
9477
|
+
const formattedMinValue = humanizeNumber({
|
|
9478
|
+
value: minValue,
|
|
9479
|
+
format: void 0
|
|
9480
|
+
}, options.locale);
|
|
9481
|
+
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
9482
|
+
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
9483
|
+
ctx.restore();
|
|
9611
9484
|
}
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9485
|
+
};
|
|
9486
|
+
|
|
9487
|
+
//#endregion
|
|
9488
|
+
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
9489
|
+
function getFunnelChartController() {
|
|
9490
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9491
|
+
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
9492
|
+
static id = "funnel";
|
|
9493
|
+
static defaults = {
|
|
9494
|
+
...globalThis.Chart?.BarController.defaults,
|
|
9495
|
+
dataElementType: "funnel",
|
|
9496
|
+
animation: { duration: (ctx) => {
|
|
9497
|
+
if (ctx.type !== "data") return 1e3;
|
|
9498
|
+
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
9499
|
+
} }
|
|
9500
|
+
};
|
|
9501
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9502
|
+
updateElements(rects, start, count, mode) {
|
|
9503
|
+
super.updateElements(rects, start, count, mode);
|
|
9504
|
+
for (let i = start; i < start + count; i++) {
|
|
9505
|
+
const rect = rects[i];
|
|
9506
|
+
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9616
9509
|
};
|
|
9617
9510
|
}
|
|
9618
|
-
function
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9511
|
+
function getFunnelChartElement() {
|
|
9512
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9513
|
+
/**
|
|
9514
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9515
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9516
|
+
*/
|
|
9517
|
+
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
9518
|
+
static id = "funnel";
|
|
9519
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9520
|
+
draw(ctx) {
|
|
9521
|
+
ctx.save();
|
|
9522
|
+
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
9523
|
+
"x",
|
|
9524
|
+
"y",
|
|
9525
|
+
"width",
|
|
9526
|
+
"height",
|
|
9527
|
+
"nextElement",
|
|
9528
|
+
"base",
|
|
9529
|
+
"options"
|
|
9530
|
+
]);
|
|
9531
|
+
const width = getElementWidth(this);
|
|
9532
|
+
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
9533
|
+
const startX = Math.min(x, base);
|
|
9534
|
+
const startY = y - height / 2;
|
|
9535
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9536
|
+
ctx.beginPath();
|
|
9537
|
+
ctx.moveTo(startX, startY);
|
|
9538
|
+
ctx.lineTo(startX + width, startY);
|
|
9539
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9540
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9541
|
+
ctx.closePath();
|
|
9542
|
+
ctx.fill();
|
|
9543
|
+
if (options.borderWidth) {
|
|
9544
|
+
ctx.strokeStyle = options.borderColor;
|
|
9545
|
+
ctx.lineWidth = options.borderWidth;
|
|
9546
|
+
ctx.stroke();
|
|
9547
|
+
}
|
|
9548
|
+
ctx.restore();
|
|
9549
|
+
}
|
|
9550
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9551
|
+
inRange(mouseX, mouseY) {
|
|
9552
|
+
const { x, y, height, nextElement, base } = this.getProps([
|
|
9553
|
+
"x",
|
|
9554
|
+
"y",
|
|
9555
|
+
"width",
|
|
9556
|
+
"height",
|
|
9557
|
+
"nextElement",
|
|
9558
|
+
"base",
|
|
9559
|
+
"options"
|
|
9560
|
+
]);
|
|
9561
|
+
const width = getElementWidth(this);
|
|
9562
|
+
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
9563
|
+
const startX = Math.min(x, base);
|
|
9564
|
+
const startY = y - height / 2;
|
|
9565
|
+
if (mouseY < startY || mouseY > startY + height) return false;
|
|
9566
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9567
|
+
const left = startX + offset * (mouseY - startY) / height;
|
|
9568
|
+
const right = startX + width - offset * (mouseY - startY) / height;
|
|
9569
|
+
if (mouseX < left || mouseX > right) return false;
|
|
9570
|
+
return true;
|
|
9571
|
+
}
|
|
9572
|
+
};
|
|
9628
9573
|
}
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9574
|
+
/**
|
|
9575
|
+
* Get an element width.
|
|
9576
|
+
*
|
|
9577
|
+
* The property width is undefined during animations, we need to compute it manually.
|
|
9578
|
+
*/
|
|
9579
|
+
function getElementWidth(element) {
|
|
9580
|
+
const { x, base } = element.getProps(["x", "base"]);
|
|
9581
|
+
return Math.max(x, base) - Math.min(x, base);
|
|
9635
9582
|
}
|
|
9583
|
+
/**
|
|
9584
|
+
* Position the tooltip inside the trapezoid.
|
|
9585
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9586
|
+
*/
|
|
9587
|
+
const funnelTooltipPositioner = function(elements) {
|
|
9588
|
+
if (!elements.length) return {
|
|
9589
|
+
x: 0,
|
|
9590
|
+
y: 0
|
|
9591
|
+
};
|
|
9592
|
+
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
9593
|
+
"x",
|
|
9594
|
+
"y",
|
|
9595
|
+
"width",
|
|
9596
|
+
"height",
|
|
9597
|
+
"base"
|
|
9598
|
+
]);
|
|
9599
|
+
const startX = Math.min(x, base);
|
|
9600
|
+
const startY = y - height / 2;
|
|
9601
|
+
return {
|
|
9602
|
+
x: startX + width * 2 / 3,
|
|
9603
|
+
y: startY + height / 2
|
|
9604
|
+
};
|
|
9605
|
+
};
|
|
9606
|
+
|
|
9607
|
+
//#endregion
|
|
9608
|
+
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
9609
|
+
const chartMinorGridPlugin = {
|
|
9610
|
+
id: "o-spreadsheet-minor-gridlines",
|
|
9611
|
+
beforeDatasetsDraw(chart) {
|
|
9612
|
+
const ctx = chart.ctx;
|
|
9613
|
+
const chartArea = chart.chartArea;
|
|
9614
|
+
if (!chartArea) return;
|
|
9615
|
+
for (const scaleId in chart.scales) {
|
|
9616
|
+
const scale = chart.scales[scaleId];
|
|
9617
|
+
const options = scale.options;
|
|
9618
|
+
const minor = options?.grid?.minor;
|
|
9619
|
+
if (!minor?.display) continue;
|
|
9620
|
+
const showMajorGrid = options?.grid?.display;
|
|
9621
|
+
const ticks = scale.ticks;
|
|
9622
|
+
if (!ticks || ticks.length < 2) continue;
|
|
9623
|
+
ctx.save();
|
|
9624
|
+
ctx.lineWidth = 1;
|
|
9625
|
+
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
9626
|
+
for (let i = 0; i < ticks.length - 1; i++) {
|
|
9627
|
+
const start = scale.getPixelForTick(i);
|
|
9628
|
+
const end = scale.getPixelForTick(i + 1);
|
|
9629
|
+
if (!isFinite(start) || !isFinite(end)) continue;
|
|
9630
|
+
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
9631
|
+
const ratio = j / 4;
|
|
9632
|
+
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
9633
|
+
ctx.beginPath();
|
|
9634
|
+
if (scale.isHorizontal()) {
|
|
9635
|
+
ctx.moveTo(position, chartArea.top);
|
|
9636
|
+
ctx.lineTo(position, chartArea.bottom);
|
|
9637
|
+
} else {
|
|
9638
|
+
ctx.moveTo(chartArea.left, position);
|
|
9639
|
+
ctx.lineTo(chartArea.right, position);
|
|
9640
|
+
}
|
|
9641
|
+
ctx.stroke();
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
ctx.restore();
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
};
|
|
9636
9648
|
|
|
9637
9649
|
//#endregion
|
|
9638
9650
|
//#region src/xlsx/constants.ts
|
|
@@ -10126,6 +10138,7 @@ function getChartBackgroundColor({ background }, getters) {
|
|
|
10126
10138
|
//#endregion
|
|
10127
10139
|
//#region src/components/figures/chart/chartJs/chartjs_show_values_plugin.ts
|
|
10128
10140
|
const MINIMAL_VERTICAL_DISTANCE = 13;
|
|
10141
|
+
const HORIZONTAL_PADDING = 3;
|
|
10129
10142
|
function isLineOverlayOnBarChart(options, dataset) {
|
|
10130
10143
|
return options.type === "bar" && dataset.type === "line";
|
|
10131
10144
|
}
|
|
@@ -10135,175 +10148,276 @@ const chartShowValuesPlugin = {
|
|
|
10135
10148
|
afterDatasetsDraw(chart, args, options) {
|
|
10136
10149
|
if (!options.showValues) return;
|
|
10137
10150
|
if (!chart._metasets?.[0]?.data) return;
|
|
10138
|
-
const ctx = chart.ctx;
|
|
10139
|
-
ctx.save();
|
|
10140
|
-
ctx.textAlign = "center";
|
|
10141
|
-
ctx.textBaseline = "middle";
|
|
10142
|
-
ctx.miterLimit = 1;
|
|
10143
10151
|
switch (options.type) {
|
|
10144
10152
|
case "pie":
|
|
10145
|
-
|
|
10153
|
+
drawPieValues(chart, options);
|
|
10146
10154
|
break;
|
|
10147
10155
|
case "line":
|
|
10148
10156
|
case "scatter":
|
|
10157
|
+
drawLineValues(chart, options);
|
|
10158
|
+
break;
|
|
10149
10159
|
case "combo":
|
|
10150
10160
|
case "waterfall":
|
|
10161
|
+
drawComboValues(chart, options);
|
|
10162
|
+
break;
|
|
10151
10163
|
case "radar":
|
|
10152
|
-
|
|
10164
|
+
drawRadarValues(chart, options);
|
|
10153
10165
|
break;
|
|
10154
10166
|
case "bar":
|
|
10155
|
-
options.horizontal ?
|
|
10167
|
+
options.horizontal ? drawHorizontalBarValues(chart, options) : drawVerticalBarValues(chart, options);
|
|
10156
10168
|
break;
|
|
10157
10169
|
case "pyramid":
|
|
10158
|
-
|
|
10170
|
+
drawHorizontalBarValues(chart, options);
|
|
10159
10171
|
break;
|
|
10160
10172
|
case "calendar":
|
|
10161
|
-
|
|
10173
|
+
drawCalendarValues(chart, options);
|
|
10162
10174
|
break;
|
|
10163
10175
|
case "bubble":
|
|
10164
|
-
|
|
10176
|
+
drawBubbleValues(chart, options);
|
|
10165
10177
|
break;
|
|
10166
10178
|
case "funnel":
|
|
10167
|
-
|
|
10179
|
+
drawFunnelValues(chart, options);
|
|
10168
10180
|
break;
|
|
10169
10181
|
}
|
|
10170
|
-
ctx.restore();
|
|
10171
10182
|
}
|
|
10172
10183
|
};
|
|
10173
|
-
function
|
|
10174
|
-
|
|
10175
|
-
ctx
|
|
10176
|
-
ctx.
|
|
10177
|
-
ctx.
|
|
10178
|
-
|
|
10179
|
-
|
|
10184
|
+
function drawValues(args) {
|
|
10185
|
+
const { chart, options, getNumberValue, direction } = args;
|
|
10186
|
+
const ctx = chart.ctx;
|
|
10187
|
+
ctx.save();
|
|
10188
|
+
ctx.textAlign = "center";
|
|
10189
|
+
ctx.textBaseline = "middle";
|
|
10190
|
+
ctx.miterLimit = 1;
|
|
10180
10191
|
const textsPositions = {};
|
|
10181
10192
|
for (const dataset of chart._metasets) {
|
|
10182
|
-
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden
|
|
10183
|
-
const yAxisScale = chart.scales[dataset.yAxisID];
|
|
10193
|
+
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
|
|
10184
10194
|
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
10185
|
-
const
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
const
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10195
|
+
const numberValue = getNumberValue(dataset, i);
|
|
10196
|
+
if (numberValue === void 0 || isNaN(numberValue)) continue;
|
|
10197
|
+
const chartElement = dataset.data[i];
|
|
10198
|
+
const elementColor = chartElement.options.backgroundColor;
|
|
10199
|
+
if (!elementColor || colorToRGBA(elementColor).a !== 1) continue;
|
|
10200
|
+
const valueToDisplay = options.callback(numberValue, dataset, i);
|
|
10201
|
+
const textSize = getTextDimensions(valueToDisplay, ctx);
|
|
10202
|
+
const callbackArgs = {
|
|
10203
|
+
dataset,
|
|
10204
|
+
chartElement,
|
|
10205
|
+
numberValue,
|
|
10206
|
+
valueIndex: i,
|
|
10207
|
+
textSize,
|
|
10208
|
+
options,
|
|
10209
|
+
chart
|
|
10210
|
+
};
|
|
10211
|
+
const position = args.getValuePosition(callbackArgs);
|
|
10212
|
+
if (args.shouldSkipValue?.(callbackArgs)) continue;
|
|
10213
|
+
if (direction === "vertical") {
|
|
10214
|
+
const key = Math.round(position.x);
|
|
10215
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10216
|
+
for (const otherPosition of textsPositions[key] || []) if (Math.abs(otherPosition - position.y) < MINIMAL_VERTICAL_DISTANCE) position.y = otherPosition + MINIMAL_VERTICAL_DISTANCE * (numberValue < 0 ? 1 : -1);
|
|
10217
|
+
textsPositions[key].push(position.y);
|
|
10218
|
+
} else {
|
|
10219
|
+
const key = Math.round(position.y);
|
|
10220
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10221
|
+
for (const otherPosition of textsPositions[key]) if (Math.abs(otherPosition - position.x) < textSize.width) position.x = numberValue < 0 ? otherPosition - textSize.width - HORIZONTAL_PADDING : otherPosition + textSize.width + HORIZONTAL_PADDING;
|
|
10222
|
+
textsPositions[key].push(position.x);
|
|
10223
|
+
}
|
|
10224
|
+
const { strokeColor, textColor } = args.getTextColors(callbackArgs);
|
|
10225
|
+
if (!!strokeColor) {
|
|
10226
|
+
ctx.strokeStyle = strokeColor;
|
|
10227
|
+
ctx.lineWidth = 3;
|
|
10228
|
+
ctx.strokeText(valueToDisplay, position.x, position.y);
|
|
10229
|
+
}
|
|
10230
|
+
ctx.fillStyle = textColor;
|
|
10231
|
+
ctx.lineWidth = 1;
|
|
10232
|
+
ctx.fillText(valueToDisplay, position.x, position.y);
|
|
10206
10233
|
}
|
|
10207
10234
|
}
|
|
10235
|
+
ctx.restore();
|
|
10208
10236
|
}
|
|
10209
|
-
function
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
}
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10237
|
+
function drawVerticalBarValues(chart, options) {
|
|
10238
|
+
drawValues({
|
|
10239
|
+
chart,
|
|
10240
|
+
options,
|
|
10241
|
+
direction: "vertical",
|
|
10242
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10243
|
+
getValuePosition: getVerticalBarValuePosition,
|
|
10244
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10245
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10246
|
+
});
|
|
10247
|
+
}
|
|
10248
|
+
function drawRadarValues(chart, options) {
|
|
10249
|
+
drawValues({
|
|
10250
|
+
chart,
|
|
10251
|
+
options,
|
|
10252
|
+
direction: "vertical",
|
|
10253
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].r),
|
|
10254
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10255
|
+
x: chartElement.x,
|
|
10256
|
+
y: chartElement.y - 10
|
|
10257
|
+
}),
|
|
10258
|
+
shouldSkipValue: () => false,
|
|
10259
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10260
|
+
});
|
|
10261
|
+
}
|
|
10262
|
+
function drawLineValues(chart, options) {
|
|
10263
|
+
drawValues({
|
|
10264
|
+
chart,
|
|
10265
|
+
options,
|
|
10266
|
+
direction: "vertical",
|
|
10267
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10268
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10269
|
+
x: chartElement.x,
|
|
10270
|
+
y: chartElement.y - 10
|
|
10271
|
+
}),
|
|
10272
|
+
shouldSkipValue: () => false,
|
|
10273
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10274
|
+
});
|
|
10275
|
+
}
|
|
10276
|
+
function drawComboValues(chart, options) {
|
|
10277
|
+
drawValues({
|
|
10278
|
+
chart,
|
|
10279
|
+
options,
|
|
10280
|
+
direction: "vertical",
|
|
10281
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10282
|
+
getValuePosition: (args) => args.dataset.type === "line" ? {
|
|
10283
|
+
x: args.chartElement.x,
|
|
10284
|
+
y: args.chartElement.y - 10
|
|
10285
|
+
} : getVerticalBarValuePosition(args),
|
|
10286
|
+
shouldSkipValue: () => false,
|
|
10287
|
+
getTextColors: (args) => args.dataset.type === "line" ? chartElementColoredTextWithChartBackgroundHalo(args) : chartBackgroundColoredTextWithElementColoredHalo(args)
|
|
10288
|
+
});
|
|
10289
|
+
}
|
|
10290
|
+
function drawCalendarValues(chart, options) {
|
|
10291
|
+
drawValues({
|
|
10292
|
+
chart,
|
|
10293
|
+
options,
|
|
10294
|
+
direction: "vertical",
|
|
10295
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10296
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10297
|
+
x: chartElement.x,
|
|
10298
|
+
y: chartElement.y + chartElement.height / 2
|
|
10299
|
+
}),
|
|
10300
|
+
shouldSkipValue: ({ chartElement, textSize }) => textSize.height + 2 > Math.abs(chartElement.height) - 2,
|
|
10301
|
+
getTextColors: ({ chartElement }) => ({
|
|
10302
|
+
strokeColor: void 0,
|
|
10303
|
+
textColor: chartFontColor(chartElement.options.backgroundColor)
|
|
10304
|
+
})
|
|
10305
|
+
});
|
|
10306
|
+
}
|
|
10307
|
+
function drawBubbleValues(chart, options) {
|
|
10308
|
+
const canDrawTextInsideBubble = (chartElement, textSize) => {
|
|
10309
|
+
const radius = chartElement.options.radius ?? globalThis.Chart?.defaults.elements.point.radius ?? 3;
|
|
10310
|
+
return textSize.height < radius * 2;
|
|
10311
|
+
};
|
|
10312
|
+
drawValues({
|
|
10313
|
+
chart,
|
|
10314
|
+
options,
|
|
10315
|
+
direction: "vertical",
|
|
10316
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10317
|
+
getValuePosition: ({ chartElement, textSize }) => ({
|
|
10318
|
+
x: chartElement.x,
|
|
10319
|
+
y: canDrawTextInsideBubble(chartElement, textSize) ? chartElement.y : chartElement.y - 10
|
|
10320
|
+
}),
|
|
10321
|
+
shouldSkipValue: () => false,
|
|
10322
|
+
getTextColors: (args) => {
|
|
10323
|
+
return canDrawTextInsideBubble(args.chartElement, args.textSize) ? chartBackgroundColoredTextWithElementColoredHalo(args) : chartElementColoredTextWithChartBackgroundHalo(args);
|
|
10324
|
+
}
|
|
10325
|
+
});
|
|
10326
|
+
}
|
|
10327
|
+
function drawHorizontalBarValues(chart, options) {
|
|
10328
|
+
drawValues({
|
|
10329
|
+
chart,
|
|
10330
|
+
options,
|
|
10331
|
+
direction: "horizontal",
|
|
10332
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10333
|
+
getValuePosition: ({ chartElement, numberValue, textSize, dataset }) => {
|
|
10334
|
+
const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
|
|
10335
|
+
const distanceFromAxisOrigin = Math.abs(chartElement.x - xZeroLine);
|
|
10272
10336
|
let xPosition;
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
}
|
|
10282
|
-
|
|
10283
|
-
}
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
}
|
|
10337
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10338
|
+
if (distanceFromAxisOrigin < textSize.width) xPosition = xZeroLine + sign * (textSize.width / 2 + HORIZONTAL_PADDING);
|
|
10339
|
+
else xPosition = chartElement.x - sign * (chartElement.width / 2);
|
|
10340
|
+
return {
|
|
10341
|
+
x: xPosition,
|
|
10342
|
+
y: chartElement.y
|
|
10343
|
+
};
|
|
10344
|
+
},
|
|
10345
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10346
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10347
|
+
});
|
|
10348
|
+
}
|
|
10349
|
+
function drawFunnelValues(chart, options) {
|
|
10350
|
+
drawValues({
|
|
10351
|
+
chart,
|
|
10352
|
+
options,
|
|
10353
|
+
direction: "horizontal",
|
|
10354
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10355
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10356
|
+
x: chartElement.x - chartElement.width / 2,
|
|
10357
|
+
y: chartElement.y
|
|
10358
|
+
}),
|
|
10359
|
+
shouldSkipValue: () => false,
|
|
10360
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10361
|
+
});
|
|
10362
|
+
}
|
|
10363
|
+
function drawPieValues(chart, options) {
|
|
10364
|
+
const getSliceDimensions = (chartElement) => {
|
|
10365
|
+
const { startAngle, endAngle, innerRadius, outerRadius } = chartElement;
|
|
10366
|
+
return {
|
|
10367
|
+
midAngle: (startAngle + endAngle) / 2,
|
|
10368
|
+
midRadius: (innerRadius + outerRadius) / 2
|
|
10369
|
+
};
|
|
10370
|
+
};
|
|
10371
|
+
drawValues({
|
|
10372
|
+
chart,
|
|
10373
|
+
options,
|
|
10374
|
+
direction: "vertical",
|
|
10375
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i]),
|
|
10376
|
+
getValuePosition: ({ chartElement }) => {
|
|
10377
|
+
const { midAngle, midRadius } = getSliceDimensions(chartElement);
|
|
10378
|
+
return {
|
|
10379
|
+
x: chartElement.x + midRadius * Math.cos(midAngle),
|
|
10380
|
+
y: chartElement.y + midRadius * Math.sin(midAngle)
|
|
10381
|
+
};
|
|
10382
|
+
},
|
|
10383
|
+
shouldSkipValue: ({ chartElement, textSize }) => {
|
|
10384
|
+
const { midRadius } = getSliceDimensions(chartElement);
|
|
10385
|
+
const sliceAngle = chartElement.endAngle - chartElement.startAngle;
|
|
10386
|
+
const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
|
|
10387
|
+
return sliceAngle < Math.PI / 2 && (textSize.width >= midWidth || midWidth < textSize.height);
|
|
10388
|
+
},
|
|
10389
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10390
|
+
});
|
|
10391
|
+
}
|
|
10392
|
+
function getTextDimensions(text, ctx) {
|
|
10393
|
+
return computeCachedTextDimension(ctx, text, computeTextFont({ fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px"));
|
|
10394
|
+
}
|
|
10395
|
+
function chartBackgroundColoredTextWithElementColoredHalo(args) {
|
|
10396
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10397
|
+
return {
|
|
10398
|
+
strokeColor: chartElement.options.backgroundColor,
|
|
10399
|
+
textColor: options.background(numberValue, dataset, valueIndex) || "#ffffff"
|
|
10400
|
+
};
|
|
10401
|
+
}
|
|
10402
|
+
function chartElementColoredTextWithChartBackgroundHalo(args) {
|
|
10403
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10404
|
+
return {
|
|
10405
|
+
strokeColor: options.background(numberValue, dataset, valueIndex) || "#ffffff",
|
|
10406
|
+
textColor: chartElement.options.backgroundColor
|
|
10407
|
+
};
|
|
10408
|
+
}
|
|
10409
|
+
function getVerticalBarValuePosition(args) {
|
|
10410
|
+
const { chartElement, numberValue, dataset, textSize, chart } = args;
|
|
10411
|
+
const yZeroLine = chart.scales[dataset.yAxisID].getPixelForValue(0);
|
|
10412
|
+
const distanceFromAxisOrigin = Math.abs(yZeroLine - chartElement.y);
|
|
10413
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10414
|
+
let yPosition = 0;
|
|
10415
|
+
if (distanceFromAxisOrigin < textSize.height) yPosition = yZeroLine - sign * (textSize.height / 2);
|
|
10416
|
+
else yPosition = chartElement.y + sign * (chartElement.height / 2);
|
|
10417
|
+
return {
|
|
10418
|
+
x: chartElement.x,
|
|
10419
|
+
y: yPosition
|
|
10420
|
+
};
|
|
10307
10421
|
}
|
|
10308
10422
|
|
|
10309
10423
|
//#endregion
|
|
@@ -11160,6 +11274,7 @@ function getRadarChartDatasets(definition, args) {
|
|
|
11160
11274
|
hidden,
|
|
11161
11275
|
borderColor,
|
|
11162
11276
|
backgroundColor: borderColor,
|
|
11277
|
+
pointBackgroundColor: borderColor,
|
|
11163
11278
|
pointRadius: definition.hideDataMarkers ? 0 : 3
|
|
11164
11279
|
};
|
|
11165
11280
|
if (fill) {
|
|
@@ -12072,11 +12187,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12072
12187
|
if (structure.baseline) {
|
|
12073
12188
|
ctx.font = structure.baseline.style.font;
|
|
12074
12189
|
ctx.fillStyle = structure.baseline.style.color;
|
|
12075
|
-
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
|
|
12190
|
+
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
|
|
12076
12191
|
}
|
|
12077
12192
|
if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
|
|
12078
12193
|
ctx.save();
|
|
12079
|
-
ctx.fillStyle = structure.baselineArrow.style.color;
|
|
12194
|
+
ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
|
|
12080
12195
|
ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
|
|
12081
12196
|
const ratio = structure.baselineArrow.style.size / 10;
|
|
12082
12197
|
ctx.scale(ratio, ratio);
|
|
@@ -12094,18 +12209,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12094
12209
|
const descr = structure.baselineDescr;
|
|
12095
12210
|
ctx.font = descr.style.font;
|
|
12096
12211
|
ctx.fillStyle = descr.style.color;
|
|
12097
|
-
ctx
|
|
12212
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
|
|
12098
12213
|
}
|
|
12099
12214
|
if (structure.key) {
|
|
12100
12215
|
ctx.font = structure.key.style.font;
|
|
12101
12216
|
ctx.fillStyle = structure.key.style.color;
|
|
12102
|
-
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
|
|
12217
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough, void 0, structure.key.style.highlightText);
|
|
12103
12218
|
}
|
|
12104
12219
|
if (structure.keyDescr) {
|
|
12105
12220
|
const descr = structure.keyDescr;
|
|
12106
12221
|
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12107
12222
|
ctx.fillStyle = descr.style.color;
|
|
12108
|
-
ctx
|
|
12223
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
|
|
12109
12224
|
}
|
|
12110
12225
|
if (structure.progressBar) {
|
|
12111
12226
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
@@ -12329,29 +12444,34 @@ var ScorecardChartConfigBuilder = class {
|
|
|
12329
12444
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12330
12445
|
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12331
12446
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12332
|
-
underline: this.runtime.keyValueStyle?.underline
|
|
12447
|
+
underline: this.runtime.keyValueStyle?.underline,
|
|
12448
|
+
highlightText: this.runtime.keyHighlight
|
|
12333
12449
|
},
|
|
12334
12450
|
keyDescr: {
|
|
12335
12451
|
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12336
12452
|
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12337
12453
|
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12338
|
-
underline: this.runtime.keyValueDescrStyle?.underline
|
|
12454
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12455
|
+
highlightText: this.runtime.keyHighlight
|
|
12339
12456
|
},
|
|
12340
12457
|
baselineValue: {
|
|
12341
12458
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12342
12459
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12343
12460
|
underline: this.runtime.baselineStyle?.underline,
|
|
12344
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12461
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12462
|
+
highlightText: this.runtime.baselineHighlight
|
|
12345
12463
|
},
|
|
12346
12464
|
baselineDescr: {
|
|
12347
12465
|
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12348
12466
|
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12349
12467
|
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12350
|
-
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
|
|
12468
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12469
|
+
highlightText: this.runtime.baselineHighlight
|
|
12351
12470
|
},
|
|
12352
12471
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
|
|
12353
12472
|
size: this.keyValue ? .8 * baselineValueFontSize : 0,
|
|
12354
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12473
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12474
|
+
highlight: this.runtime.baselineHighlight
|
|
12355
12475
|
}
|
|
12356
12476
|
};
|
|
12357
12477
|
}
|
|
@@ -12447,11 +12567,14 @@ var ScorecardChart = class extends Component {
|
|
|
12447
12567
|
});
|
|
12448
12568
|
onWillUnmount(() => resizeObserver.disconnect());
|
|
12449
12569
|
}
|
|
12570
|
+
config(canvasRect, zoom) {
|
|
12571
|
+
return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
|
|
12572
|
+
}
|
|
12450
12573
|
createChart() {
|
|
12451
12574
|
const canvas = this.canvas();
|
|
12452
12575
|
if (!canvas) return;
|
|
12453
12576
|
const zoom = this.env.model.getters.getViewportZoomLevel();
|
|
12454
|
-
drawScoreChart(
|
|
12577
|
+
drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
|
|
12455
12578
|
}
|
|
12456
12579
|
};
|
|
12457
12580
|
|
|
@@ -28055,7 +28178,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
|
|
|
28055
28178
|
*/
|
|
28056
28179
|
function filterInvalidHierarchicalPoints(values, hierarchy) {
|
|
28057
28180
|
const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
|
|
28058
|
-
const isEmpty = (value) => value === null || value === "";
|
|
28181
|
+
const isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
28059
28182
|
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
|
|
28060
28183
|
const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
|
|
28061
28184
|
if (isEmpty(groups[0]?.value)) return false;
|
|
@@ -28619,23 +28742,11 @@ function getChartShowValues(definition, args) {
|
|
|
28619
28742
|
}
|
|
28620
28743
|
function getCalendarChartShowValues(definition, args) {
|
|
28621
28744
|
const { locale, axisFormats } = args;
|
|
28622
|
-
let background = (_value, dataset, index) => definition.background;
|
|
28623
|
-
const values = args.dataSetsValues.flat().flatMap((dsv) => dsv?.data.filter(isNumberResult)).map((cell) => cell.value);
|
|
28624
|
-
if (values.length) {
|
|
28625
|
-
const min = Math.min(...values);
|
|
28626
|
-
const max = Math.max(...values);
|
|
28627
|
-
const colorScale = getRuntimeColorScale(definition.colorScale ?? schemeToColorScale("oranges"), min, max);
|
|
28628
|
-
background = (_value, dataset, index) => {
|
|
28629
|
-
const value = dataset._dataset.values[index];
|
|
28630
|
-
if (value === void 0) return definition.background;
|
|
28631
|
-
return chartFontColor(colorScale(value));
|
|
28632
|
-
};
|
|
28633
|
-
}
|
|
28634
28745
|
return {
|
|
28635
28746
|
type: "calendar",
|
|
28636
28747
|
horizontal: false,
|
|
28637
28748
|
showValues: "showValues" in definition ? !!definition.showValues : false,
|
|
28638
|
-
background,
|
|
28749
|
+
background: () => definition.background,
|
|
28639
28750
|
callback: (_value, dataset, index) => {
|
|
28640
28751
|
const value = dataset._dataset.values[index];
|
|
28641
28752
|
return value === void 0 ? "" : humanizeNumber({
|
|
@@ -34536,6 +34647,35 @@ var CellIsRuleEditor = class extends Component {
|
|
|
34536
34647
|
}
|
|
34537
34648
|
};
|
|
34538
34649
|
|
|
34650
|
+
//#endregion
|
|
34651
|
+
//#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
|
|
34652
|
+
var CalendarButton = class extends Component {
|
|
34653
|
+
static template = "o-spreadsheet-CalendarButton";
|
|
34654
|
+
props = props({
|
|
34655
|
+
value: types$1.string().optional(""),
|
|
34656
|
+
onChange: types$1.function()
|
|
34657
|
+
});
|
|
34658
|
+
datePickerRef = signal.ref(HTMLInputElement);
|
|
34659
|
+
openCalendar() {
|
|
34660
|
+
this.datePickerRef()?.showPicker();
|
|
34661
|
+
}
|
|
34662
|
+
formatDateForInput(value) {
|
|
34663
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34664
|
+
return dateValue ? formatValue(dateValue.value, {
|
|
34665
|
+
format: "yyyy-mm-dd",
|
|
34666
|
+
locale: DEFAULT_LOCALE
|
|
34667
|
+
}) : "";
|
|
34668
|
+
}
|
|
34669
|
+
onDateInputValueChanged(value) {
|
|
34670
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34671
|
+
const formattedValue = dateValue ? formatValue(dateValue.value, {
|
|
34672
|
+
format: DEFAULT_LOCALE.dateFormat,
|
|
34673
|
+
locale: DEFAULT_LOCALE
|
|
34674
|
+
}) : "";
|
|
34675
|
+
this.props.onChange(formattedValue);
|
|
34676
|
+
}
|
|
34677
|
+
};
|
|
34678
|
+
|
|
34539
34679
|
//#endregion
|
|
34540
34680
|
//#region src/components/side_panel/criterion_form/criterion_form.ts
|
|
34541
34681
|
var CriterionForm = class extends Component {
|
|
@@ -35224,7 +35364,8 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35224
35364
|
static template = "o-spreadsheet-DataValidationDateCriterion";
|
|
35225
35365
|
static components = {
|
|
35226
35366
|
CriterionInput,
|
|
35227
|
-
Select
|
|
35367
|
+
Select,
|
|
35368
|
+
CalendarButton
|
|
35228
35369
|
};
|
|
35229
35370
|
get currentDateValue() {
|
|
35230
35371
|
return this.props.criterion.dateValue || "exactDate";
|
|
@@ -35250,7 +35391,10 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35250
35391
|
//#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
|
|
35251
35392
|
var DoubleInputCriterionForm = class extends CriterionForm {
|
|
35252
35393
|
static template = "o-spreadsheet-DoubleInputCriterionForm";
|
|
35253
|
-
static components = {
|
|
35394
|
+
static components = {
|
|
35395
|
+
CriterionInput,
|
|
35396
|
+
CalendarButton
|
|
35397
|
+
};
|
|
35254
35398
|
onFirstValueChanged(value) {
|
|
35255
35399
|
const values = this.props.criterion.values;
|
|
35256
35400
|
this.updateCriterion({ values: [value, values[1] || ""] });
|
|
@@ -35259,6 +35403,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
|
|
|
35259
35403
|
const values = this.props.criterion.values;
|
|
35260
35404
|
this.updateCriterion({ values: [values[0] || "", value] });
|
|
35261
35405
|
}
|
|
35406
|
+
get isDateType() {
|
|
35407
|
+
return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
|
|
35408
|
+
}
|
|
35262
35409
|
};
|
|
35263
35410
|
|
|
35264
35411
|
//#endregion
|
|
@@ -46605,7 +46752,7 @@ var FiguresContainer = class extends Component {
|
|
|
46605
46752
|
horizontalSnap: void 0,
|
|
46606
46753
|
verticalSnap: void 0,
|
|
46607
46754
|
cancelDnd: void 0,
|
|
46608
|
-
|
|
46755
|
+
overlappingChartOrCarousel: void 0
|
|
46609
46756
|
});
|
|
46610
46757
|
setup() {
|
|
46611
46758
|
onMounted(() => {
|
|
@@ -46621,7 +46768,7 @@ var FiguresContainer = class extends Component {
|
|
|
46621
46768
|
this.dnd.selectedRect = void 0;
|
|
46622
46769
|
this.dnd.horizontalSnap = void 0;
|
|
46623
46770
|
this.dnd.verticalSnap = void 0;
|
|
46624
|
-
this.dnd.
|
|
46771
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46625
46772
|
this.dnd.cancelDnd = void 0;
|
|
46626
46773
|
}
|
|
46627
46774
|
});
|
|
@@ -46759,11 +46906,11 @@ var FiguresContainer = class extends Component {
|
|
|
46759
46906
|
hasStartedDnd = true;
|
|
46760
46907
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
|
|
46761
46908
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
46762
|
-
let
|
|
46909
|
+
let overlappingChartOrCarousel = void 0;
|
|
46763
46910
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
46764
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart"))
|
|
46765
|
-
this.dnd.
|
|
46766
|
-
if (!
|
|
46911
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
|
|
46912
|
+
this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
|
|
46913
|
+
if (!overlappingChartOrCarousel) {
|
|
46767
46914
|
const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
|
|
46768
46915
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
46769
46916
|
this.dnd.selectedRect = this.getDndFigureRect();
|
|
@@ -46784,7 +46931,7 @@ var FiguresContainer = class extends Component {
|
|
|
46784
46931
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
46785
46932
|
return;
|
|
46786
46933
|
}
|
|
46787
|
-
if (!this.dnd.
|
|
46934
|
+
if (!this.dnd.overlappingChartOrCarousel) {
|
|
46788
46935
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
46789
46936
|
return {
|
|
46790
46937
|
sheetId,
|
|
@@ -46794,20 +46941,25 @@ var FiguresContainer = class extends Component {
|
|
|
46794
46941
|
}) || [];
|
|
46795
46942
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
46796
46943
|
} else {
|
|
46797
|
-
const
|
|
46944
|
+
const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
|
|
46798
46945
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
46799
|
-
this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46946
|
+
if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46800
46947
|
sheetId,
|
|
46801
|
-
carouselFigureId,
|
|
46948
|
+
carouselFigureId: overlappingFigureId,
|
|
46802
46949
|
chartFigureIds
|
|
46803
46950
|
});
|
|
46951
|
+
else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
46952
|
+
sheetId,
|
|
46953
|
+
baseFigureId: overlappingFigureId,
|
|
46954
|
+
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
46955
|
+
});
|
|
46804
46956
|
}
|
|
46805
46957
|
this.dnd.draggedFigure = void 0;
|
|
46806
46958
|
this.dnd.selectedFigures = void 0;
|
|
46807
46959
|
this.dnd.selectedRect = void 0;
|
|
46808
46960
|
this.dnd.horizontalSnap = void 0;
|
|
46809
46961
|
this.dnd.verticalSnap = void 0;
|
|
46810
|
-
this.dnd.
|
|
46962
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46811
46963
|
};
|
|
46812
46964
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46813
46965
|
}
|
|
@@ -46863,7 +47015,7 @@ var FiguresContainer = class extends Component {
|
|
|
46863
47015
|
this.dnd.selectedRect = void 0;
|
|
46864
47016
|
this.dnd.horizontalSnap = void 0;
|
|
46865
47017
|
this.dnd.verticalSnap = void 0;
|
|
46866
|
-
this.dnd.
|
|
47018
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46867
47019
|
};
|
|
46868
47020
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46869
47021
|
}
|
|
@@ -46873,12 +47025,12 @@ var FiguresContainer = class extends Component {
|
|
|
46873
47025
|
getFigureStyle(figureUI) {
|
|
46874
47026
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
46875
47027
|
return cssPropertiesToCss({
|
|
46876
|
-
opacity: this.dnd.
|
|
47028
|
+
opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
|
|
46877
47029
|
cursor: "grabbing"
|
|
46878
47030
|
});
|
|
46879
47031
|
}
|
|
46880
47032
|
getFigureClass(figureUI) {
|
|
46881
|
-
if (figureUI.id !== this.dnd.
|
|
47033
|
+
if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
|
|
46882
47034
|
return "o-add-to-carousel";
|
|
46883
47035
|
}
|
|
46884
47036
|
getSnap(snapLine) {
|
|
@@ -46921,18 +47073,18 @@ var FiguresContainer = class extends Component {
|
|
|
46921
47073
|
height: `100%`
|
|
46922
47074
|
});
|
|
46923
47075
|
}
|
|
46924
|
-
|
|
47076
|
+
getOverlappingFigure(figureUI, otherFigures) {
|
|
46925
47077
|
if (figureUI.tag !== "chart") return;
|
|
46926
47078
|
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
46927
47079
|
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
46928
47080
|
let bestMatch;
|
|
46929
47081
|
let smallestDistance = Infinity;
|
|
46930
47082
|
for (const figure of otherFigures) {
|
|
46931
|
-
if (figure.tag !== "carousel") continue;
|
|
46932
|
-
const
|
|
46933
|
-
const
|
|
46934
|
-
const distanceX = Math.abs(figureCenterX -
|
|
46935
|
-
const distanceY = Math.abs(figureCenterY -
|
|
47083
|
+
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
47084
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
47085
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
47086
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
47087
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
46936
47088
|
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
46937
47089
|
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
46938
47090
|
smallestDistance = squaredDistance;
|
|
@@ -53025,11 +53177,16 @@ var DefaultPlugin = class extends CorePlugin {
|
|
|
53025
53177
|
const deltaStyle = {};
|
|
53026
53178
|
let hasDelta = false;
|
|
53027
53179
|
const styleSheet = this.style[position.sheetId];
|
|
53028
|
-
if (!styleSheet) continue;
|
|
53029
53180
|
for (const key in newDefaultStyle) {
|
|
53030
53181
|
if (key in cellStyle) continue;
|
|
53031
|
-
const defaults = styleSheet[key];
|
|
53032
|
-
if (!defaults)
|
|
53182
|
+
const defaults = styleSheet?.[key];
|
|
53183
|
+
if (!defaults) {
|
|
53184
|
+
if (newDefaultStyle[key] !== DEFAULT_STYLE[key]) {
|
|
53185
|
+
deltaStyle[key] = DEFAULT_STYLE[key];
|
|
53186
|
+
hasDelta = true;
|
|
53187
|
+
}
|
|
53188
|
+
continue;
|
|
53189
|
+
}
|
|
53033
53190
|
const rowDefault = defaults.rowDefault?.[position.row];
|
|
53034
53191
|
if (rowDefault !== void 0) {
|
|
53035
53192
|
if (priorities.shouldUseDefaultRow) {
|
|
@@ -53071,7 +53228,8 @@ var DefaultPlugin = class extends CorePlugin {
|
|
|
53071
53228
|
for (const key in styleSheet) if (!(key in style)) {
|
|
53072
53229
|
const defaults = styleSheet[key];
|
|
53073
53230
|
if (!defaults) continue;
|
|
53074
|
-
|
|
53231
|
+
const styleValue = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
|
|
53232
|
+
if (styleValue !== void 0) style[key] = styleValue;
|
|
53075
53233
|
}
|
|
53076
53234
|
return style;
|
|
53077
53235
|
}
|
|
@@ -58719,13 +58877,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
|
|
|
58719
58877
|
const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
|
|
58720
58878
|
const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
|
|
58721
58879
|
const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
|
|
58722
|
-
if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58880
|
+
if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58723
58881
|
const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
|
|
58724
58882
|
const colorThresholds = [{
|
|
58725
58883
|
value: minValue,
|
|
58726
58884
|
color: rule.minimum.color
|
|
58727
58885
|
}];
|
|
58728
|
-
if (rule.midpoint && midValue) colorThresholds.push({
|
|
58886
|
+
if (rule.midpoint && midValue !== null) colorThresholds.push({
|
|
58729
58887
|
value: midValue,
|
|
58730
58888
|
color: rule.midpoint.color
|
|
58731
58889
|
});
|
|
@@ -66184,7 +66342,9 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66184
66342
|
ctx.lineWidth = 1.5 * thinLineWidth;
|
|
66185
66343
|
const isDarkMode = this.getters.isDarkMode();
|
|
66186
66344
|
for (const zone of zones) {
|
|
66345
|
+
if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
|
|
66187
66346
|
const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
|
|
66347
|
+
const currentLineWidth = ctx.lineWidth;
|
|
66188
66348
|
if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
|
|
66189
66349
|
if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
|
|
66190
66350
|
if (height === 0 && width === 0) {
|
|
@@ -66198,6 +66358,7 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66198
66358
|
ctx.globalCompositeOperation = "source-over";
|
|
66199
66359
|
ctx.strokeRect(x, y, width, height);
|
|
66200
66360
|
}
|
|
66361
|
+
ctx.lineWidth = currentLineWidth;
|
|
66201
66362
|
}
|
|
66202
66363
|
ctx.globalCompositeOperation = "source-over";
|
|
66203
66364
|
const position = renderingContext.activePosition;
|
|
@@ -66344,6 +66505,9 @@ var InternalViewport = class {
|
|
|
66344
66505
|
this.adjustViewportZoneX();
|
|
66345
66506
|
this.adjustViewportZoneY();
|
|
66346
66507
|
}
|
|
66508
|
+
isZoneVisible(zone) {
|
|
66509
|
+
return intersection(zone, this) !== void 0;
|
|
66510
|
+
}
|
|
66347
66511
|
/**
|
|
66348
66512
|
*
|
|
66349
66513
|
* Computes the visible coordinates & dimensions of a given zone inside the viewport
|
|
@@ -66624,6 +66788,9 @@ var ViewportCollection = class {
|
|
|
66624
66788
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
66625
66789
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
66626
66790
|
}
|
|
66791
|
+
isZoneVisibleInViewport(sheetId, zone) {
|
|
66792
|
+
return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
|
|
66793
|
+
}
|
|
66627
66794
|
getScrollBarWidth() {
|
|
66628
66795
|
return 15 / this.zoomLevel;
|
|
66629
66796
|
}
|
|
@@ -70697,18 +70864,28 @@ var NamedRangeSelector = class extends Component {
|
|
|
70697
70864
|
return;
|
|
70698
70865
|
}
|
|
70699
70866
|
const activeSheetId = this.env.model.getters.getActiveSheetId();
|
|
70700
|
-
if (activeSheetId !== sheetId)
|
|
70701
|
-
|
|
70702
|
-
|
|
70703
|
-
|
|
70704
|
-
|
|
70867
|
+
if (activeSheetId !== sheetId) {
|
|
70868
|
+
if (!this.env.model.getters.getSheet(sheetId).isVisible) {
|
|
70869
|
+
this.env.notifyUser({
|
|
70870
|
+
text: _t("The sheet on which the range is defined is hidden."),
|
|
70871
|
+
type: "info",
|
|
70872
|
+
sticky: false
|
|
70873
|
+
});
|
|
70874
|
+
return;
|
|
70875
|
+
}
|
|
70876
|
+
this.env.model.dispatch("ACTIVATE_SHEET", {
|
|
70877
|
+
sheetIdFrom: activeSheetId,
|
|
70878
|
+
sheetIdTo: sheetId
|
|
70879
|
+
});
|
|
70880
|
+
}
|
|
70881
|
+
this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
|
|
70705
70882
|
this.env.model.selection.selectZone({
|
|
70706
70883
|
cell: {
|
|
70707
70884
|
col: zone.left,
|
|
70708
70885
|
row: zone.top
|
|
70709
70886
|
},
|
|
70710
70887
|
zone
|
|
70711
|
-
});
|
|
70888
|
+
}, { allowsHiddenSelection: true });
|
|
70712
70889
|
}
|
|
70713
70890
|
get selectionKey() {
|
|
70714
70891
|
return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
|
|
@@ -76987,8 +77164,8 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
76987
77164
|
for (const key in DEFAULT_STYLE) {
|
|
76988
77165
|
content.style[key] = {
|
|
76989
77166
|
sheetDefault: this.getters.getDefaultStyle(data.sheetId, key, "SHEET", void 0) ?? DEFAULT_STYLE[key],
|
|
76990
|
-
colDefault:
|
|
76991
|
-
rowDefault:
|
|
77167
|
+
colDefault: [],
|
|
77168
|
+
rowDefault: []
|
|
76992
77169
|
};
|
|
76993
77170
|
let colIndex = 0;
|
|
76994
77171
|
for (const col of data.columnsIndexes) {
|
|
@@ -77005,20 +77182,41 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
77005
77182
|
}
|
|
77006
77183
|
return content;
|
|
77007
77184
|
}
|
|
77008
|
-
|
|
77009
|
-
|
|
77010
|
-
|
|
77011
|
-
|
|
77012
|
-
|
|
77013
|
-
|
|
77185
|
+
adaptContentToZone(zone, content) {
|
|
77186
|
+
const colRepetition = Math.max(Math.floor((zone.right - zone.left + 1) / content.width), 1);
|
|
77187
|
+
const rowRepetition = Math.max(Math.floor((zone.bottom - zone.top + 1) / content.height), 1);
|
|
77188
|
+
if (colRepetition === 1 && rowRepetition === 1) return content;
|
|
77189
|
+
const newContent = deepCopy(content);
|
|
77190
|
+
newContent.height *= rowRepetition;
|
|
77191
|
+
newContent.width *= colRepetition;
|
|
77192
|
+
if (rowRepetition > 1 && newContent.format.rowDefault) {
|
|
77193
|
+
newContent.format.rowDefault.length = content.height;
|
|
77194
|
+
newContent.format.rowDefault = repeat(newContent.format.rowDefault, rowRepetition);
|
|
77195
|
+
}
|
|
77196
|
+
if (colRepetition > 1 && newContent.format.colDefault) {
|
|
77197
|
+
newContent.format.colDefault.length = content.width;
|
|
77198
|
+
newContent.format.colDefault = repeat(newContent.format.colDefault, colRepetition);
|
|
77199
|
+
}
|
|
77200
|
+
for (const key in content.style) {
|
|
77201
|
+
if (rowRepetition > 1 && newContent.style[key].rowDefault) {
|
|
77202
|
+
newContent.style[key].rowDefault.length = content.height;
|
|
77203
|
+
newContent.style[key].rowDefault = repeat(newContent.style[key].rowDefault, rowRepetition);
|
|
77204
|
+
}
|
|
77205
|
+
if (colRepetition > 1 && newContent.style[key].colDefault) {
|
|
77206
|
+
newContent.style[key].colDefault.length = content.width;
|
|
77207
|
+
newContent.style[key].colDefault = repeat(newContent.style[key].colDefault, colRepetition);
|
|
77208
|
+
}
|
|
77209
|
+
}
|
|
77210
|
+
return newContent;
|
|
77014
77211
|
}
|
|
77015
77212
|
paste(target, content, options) {
|
|
77016
77213
|
const sheetId = target.sheetId;
|
|
77017
77214
|
if (options.pasteOption === "asValue") return;
|
|
77018
77215
|
const zones = target.zones;
|
|
77019
|
-
if (!options.isCutOperation) for (const zone of zones)
|
|
77020
|
-
|
|
77021
|
-
this.
|
|
77216
|
+
if (!options.isCutOperation) for (const zone of zones) {
|
|
77217
|
+
const newContent = this.adaptContentToZone(zone, content);
|
|
77218
|
+
this.pasteStyle(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.style);
|
|
77219
|
+
this.pasteFormat(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.format);
|
|
77022
77220
|
}
|
|
77023
77221
|
else {
|
|
77024
77222
|
this.clearClippedZones(content);
|
|
@@ -77131,7 +77329,7 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
77131
77329
|
const commands = [];
|
|
77132
77330
|
for (const [zoneStr, [zone, priority]] of Object.entries(zones)) {
|
|
77133
77331
|
const format = updateCells[zoneStr];
|
|
77134
|
-
commands.push([
|
|
77332
|
+
if (format !== void 0) commands.push([
|
|
77135
77333
|
priority,
|
|
77136
77334
|
zone,
|
|
77137
77335
|
format
|
|
@@ -87333,7 +87531,9 @@ const helpers = {
|
|
|
87333
87531
|
collapseHierarchicalDisplayName,
|
|
87334
87532
|
getCanonicalSymbolName,
|
|
87335
87533
|
fuzzyLookup,
|
|
87336
|
-
replaceSymbolInFormula
|
|
87534
|
+
replaceSymbolInFormula,
|
|
87535
|
+
isSingleCellReference,
|
|
87536
|
+
computeCachedTextDimension
|
|
87337
87537
|
};
|
|
87338
87538
|
const links = {
|
|
87339
87539
|
isMarkdownLink,
|
|
@@ -87403,7 +87603,8 @@ const components = {
|
|
|
87403
87603
|
FullScreenFigure,
|
|
87404
87604
|
NumberInput,
|
|
87405
87605
|
TopBar,
|
|
87406
|
-
Composer
|
|
87606
|
+
Composer,
|
|
87607
|
+
CalendarButton
|
|
87407
87608
|
};
|
|
87408
87609
|
const hooks = {
|
|
87409
87610
|
useDragAndDropListItems,
|
|
@@ -87463,6 +87664,6 @@ const chartHelpers = {
|
|
|
87463
87664
|
//#endregion
|
|
87464
87665
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, BadExpressionError, CHART_TYPES, CellErrorType, CellValueType, CircularDependencyError, ClientDisconnectedError, ClipboardMIMEType, CommandResult, CompiledFormula, CorePlugin, CoreViewPlugin, DEFAULT_LOCALE, DEFAULT_LOCALES, DEFAULT_LOCALE_DIGIT_GROUPING, DIRECTION, DispatchResult, DivisionByZeroError, EvaluationError, InvalidReferenceError, LocalTransportService, Model, NEXT_VALUE, NotAvailableError, NumberTooLargeError, OrderedLayers, PREVIOUS_VALUE, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, SplillBlockedError, Spreadsheet, SpreadsheetPivotTable, UIPlugin, UnknownFunctionError, __info__, addFunction, addRenderingLayer, astToFormula, availableConditionalFormatOperators, availableDataValidationOperators, availableFiltersOperators, borderPositions, borderStyles, canExecuteInReadonly, categories, chartHelpers, compatibility, components, composerFocusTypes, constants, convertAstNodes, coreTypes, createAutocompleteArgumentsProvider, errorTypes, filterDateCriterionOperators, filterNumberCriterionOperators, filterTextCriterionOperators, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidSubtotalFormulasCommands, invalidateBordersCommands, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, isCoreCommand, isHeadersDependant, isMatrix, isPositionDependent, isRangeDependant, isSheetDependent, isTargetDependent, isZoneDependent, iterateAstNodes, links, load, lockedSheetAllowedCommands, parse, parseTokens, readonlyAllowedCommands, registries, schemeToColorScale, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
|
|
87465
87666
|
|
|
87466
|
-
__info__.version = "19.5.0-alpha.
|
|
87467
|
-
__info__.date = "2026-07-
|
|
87468
|
-
__info__.hash = "
|
|
87667
|
+
__info__.version = "19.5.0-alpha.3";
|
|
87668
|
+
__info__.date = "2026-07-14T10:17:06.949Z";
|
|
87669
|
+
__info__.hash = "c029184";
|