@odoo/o-spreadsheet 19.5.0-alpha.0 → 19.5.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o_spreadsheet.cjs +781 -704
- package/dist/o_spreadsheet.css +22 -5
- package/dist/o_spreadsheet.esm.js +779 -702
- package/dist/o_spreadsheet.iife.js +781 -704
- package/dist/o_spreadsheet.min.iife.js +257 -257
- package/dist/o_spreadsheet.xml +59 -26
- 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/pivot/pivot_helpers.d.ts +2 -2
- 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 +7 -3
- package/dist/types/types/chart/scorecard_chart.d.ts +2 -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-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.5.0-alpha.2
|
|
6
|
+
* @date 2026-07-13T06:26:20.354Z
|
|
7
|
+
* @hash a4d2f90
|
|
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 = {
|
|
@@ -8405,534 +8406,6 @@ function addOrigin(cell, origin) {
|
|
|
8405
8406
|
return cell;
|
|
8406
8407
|
}
|
|
8407
8408
|
|
|
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
8409
|
//#endregion
|
|
8937
8410
|
//#region src/helpers/color.ts
|
|
8938
8411
|
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
|
|
@@ -9497,142 +8970,675 @@ const ALTERNATING_COLORS_XL = [
|
|
|
9497
8970
|
function getNthColor(index, palette) {
|
|
9498
8971
|
return palette[index % palette.length];
|
|
9499
8972
|
}
|
|
9500
|
-
function getColorsPalette(quantity) {
|
|
9501
|
-
if (quantity <= 6) return COLORS_SM;
|
|
9502
|
-
else if (quantity <= 12) return COLORS_MD;
|
|
9503
|
-
else if (quantity <= 24) return COLORS_LG;
|
|
9504
|
-
else return COLORS_XL;
|
|
8973
|
+
function getColorsPalette(quantity) {
|
|
8974
|
+
if (quantity <= 6) return COLORS_SM;
|
|
8975
|
+
else if (quantity <= 12) return COLORS_MD;
|
|
8976
|
+
else if (quantity <= 24) return COLORS_LG;
|
|
8977
|
+
else return COLORS_XL;
|
|
8978
|
+
}
|
|
8979
|
+
function getAlternatingColorsPalette(quantity) {
|
|
8980
|
+
if (quantity <= 6) return COLORS_SM;
|
|
8981
|
+
else if (quantity <= 12) return ALTERNATING_COLORS_MD;
|
|
8982
|
+
else if (quantity <= 24) return ALTERNATING_COLORS_LG;
|
|
8983
|
+
else return ALTERNATING_COLORS_XL;
|
|
8984
|
+
}
|
|
8985
|
+
var ColorGenerator = class {
|
|
8986
|
+
preferredColors;
|
|
8987
|
+
currentColorIndex = 0;
|
|
8988
|
+
palette;
|
|
8989
|
+
constructor(paletteSize, preferredColors = []) {
|
|
8990
|
+
this.preferredColors = preferredColors;
|
|
8991
|
+
this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
8992
|
+
}
|
|
8993
|
+
next() {
|
|
8994
|
+
return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
|
|
8995
|
+
}
|
|
8996
|
+
};
|
|
8997
|
+
var AlternatingColorGenerator = class extends ColorGenerator {
|
|
8998
|
+
constructor(paletteSize, preferredColors = []) {
|
|
8999
|
+
super(paletteSize, preferredColors);
|
|
9000
|
+
this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
9001
|
+
}
|
|
9002
|
+
};
|
|
9003
|
+
var AlternatingColorMap = class {
|
|
9004
|
+
availableColors;
|
|
9005
|
+
colors = {};
|
|
9006
|
+
constructor(paletteSize = 12) {
|
|
9007
|
+
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9008
|
+
}
|
|
9009
|
+
get(id) {
|
|
9010
|
+
if (!this.colors[id]) this.colors[id] = this.availableColors.next();
|
|
9011
|
+
return this.colors[id];
|
|
9012
|
+
}
|
|
9013
|
+
};
|
|
9014
|
+
const COLORSCHEMES = {
|
|
9015
|
+
greys: [
|
|
9016
|
+
"#ffffff",
|
|
9017
|
+
"#808080",
|
|
9018
|
+
"#000000"
|
|
9019
|
+
],
|
|
9020
|
+
blues: [
|
|
9021
|
+
"#f7fbff",
|
|
9022
|
+
"#6aaed6",
|
|
9023
|
+
"#08306b"
|
|
9024
|
+
],
|
|
9025
|
+
reds: [
|
|
9026
|
+
"#fff5f0",
|
|
9027
|
+
"#fb694a",
|
|
9028
|
+
"#67000d"
|
|
9029
|
+
],
|
|
9030
|
+
greens: [
|
|
9031
|
+
"#f7fcf5",
|
|
9032
|
+
"#73c476",
|
|
9033
|
+
"#00441b"
|
|
9034
|
+
],
|
|
9035
|
+
oranges: [
|
|
9036
|
+
"#fff5eb",
|
|
9037
|
+
"#fd8c3b",
|
|
9038
|
+
"#7f2704"
|
|
9039
|
+
],
|
|
9040
|
+
purples: [
|
|
9041
|
+
"#fcfbfd",
|
|
9042
|
+
"#9e9ac8",
|
|
9043
|
+
"#3f007d"
|
|
9044
|
+
],
|
|
9045
|
+
viridis: [
|
|
9046
|
+
"#440154",
|
|
9047
|
+
"#21918c",
|
|
9048
|
+
"#fde725"
|
|
9049
|
+
],
|
|
9050
|
+
cividis: [
|
|
9051
|
+
"#00224e",
|
|
9052
|
+
"#7d7c78",
|
|
9053
|
+
"#fee838"
|
|
9054
|
+
],
|
|
9055
|
+
rainbow: [
|
|
9056
|
+
"#B41DB4",
|
|
9057
|
+
"#FFFF00",
|
|
9058
|
+
"#00FFFF"
|
|
9059
|
+
]
|
|
9060
|
+
};
|
|
9061
|
+
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9062
|
+
/**
|
|
9063
|
+
* Returns a function that maps a value to a color using a color scale defined by the given
|
|
9064
|
+
* color/threshold values pairs.
|
|
9065
|
+
*/
|
|
9066
|
+
function getColorScale(colorScalePoints) {
|
|
9067
|
+
if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
|
|
9068
|
+
const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
|
|
9069
|
+
const thresholds = [];
|
|
9070
|
+
for (let i = 1; i < sortedColorScalePoints.length; i++) {
|
|
9071
|
+
const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
|
|
9072
|
+
const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
|
|
9073
|
+
const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
|
|
9074
|
+
const maxColor = colorToNumber(sortedColorScalePoints[i].color);
|
|
9075
|
+
thresholds.push({
|
|
9076
|
+
min: sortedColorScalePoints[i - 1].value,
|
|
9077
|
+
max: sortedColorScalePoints[i].value,
|
|
9078
|
+
minColor,
|
|
9079
|
+
maxColor,
|
|
9080
|
+
minColorAlpha,
|
|
9081
|
+
maxColorAlpha,
|
|
9082
|
+
colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
|
|
9083
|
+
});
|
|
9084
|
+
}
|
|
9085
|
+
return (value) => {
|
|
9086
|
+
if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
|
|
9087
|
+
for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
|
|
9088
|
+
return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
|
|
9089
|
+
};
|
|
9090
|
+
}
|
|
9091
|
+
function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
|
|
9092
|
+
const deltaValue = maxValue - minValue;
|
|
9093
|
+
const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
|
|
9094
|
+
const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
|
|
9095
|
+
const deltaColorB = minColor % 256 - maxColor % 256;
|
|
9096
|
+
return [
|
|
9097
|
+
deltaColorR / deltaValue,
|
|
9098
|
+
deltaColorG / deltaValue,
|
|
9099
|
+
deltaColorB / deltaValue
|
|
9100
|
+
];
|
|
9101
|
+
}
|
|
9102
|
+
function colorCell(value, minValue, minColor, colorDiffUnit) {
|
|
9103
|
+
const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
|
|
9104
|
+
const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
|
|
9105
|
+
const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
|
|
9106
|
+
const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
|
|
9107
|
+
return r << 16 | g << 8 | b;
|
|
9108
|
+
}
|
|
9109
|
+
|
|
9110
|
+
//#endregion
|
|
9111
|
+
//#region src/helpers/text_helper.ts
|
|
9112
|
+
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
9113
|
+
return numberOfLines * (textLineHeight + 4) - 4;
|
|
9114
|
+
}
|
|
9115
|
+
function getCanvas(width = 100, height = 100) {
|
|
9116
|
+
return new OffscreenCanvas(width, height).getContext("2d");
|
|
9117
|
+
}
|
|
9118
|
+
/**
|
|
9119
|
+
* Get the default height of the cell given its style.
|
|
9120
|
+
*/
|
|
9121
|
+
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
9122
|
+
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
9123
|
+
let content = "";
|
|
9124
|
+
try {
|
|
9125
|
+
if (!cell.isFormula) {
|
|
9126
|
+
const localeFormat = {
|
|
9127
|
+
format: cell.format,
|
|
9128
|
+
locale
|
|
9129
|
+
};
|
|
9130
|
+
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
9131
|
+
}
|
|
9132
|
+
} catch {
|
|
9133
|
+
content = CellErrorType.GenericError;
|
|
9134
|
+
}
|
|
9135
|
+
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
9136
|
+
}
|
|
9137
|
+
function getCellContentHeight(ctx, content, style, colSize) {
|
|
9138
|
+
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
9139
|
+
}
|
|
9140
|
+
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
9141
|
+
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
9142
|
+
}
|
|
9143
|
+
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
9144
|
+
if (!textLines.length) return {
|
|
9145
|
+
width: 0,
|
|
9146
|
+
height: 0
|
|
9147
|
+
};
|
|
9148
|
+
const font = computeTextFont(style, fontUnit);
|
|
9149
|
+
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
9150
|
+
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
9151
|
+
const width = Math.max(...sizes.map((size) => size.width));
|
|
9152
|
+
if (!style.rotation) return {
|
|
9153
|
+
height,
|
|
9154
|
+
width
|
|
9155
|
+
};
|
|
9156
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9157
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9158
|
+
return {
|
|
9159
|
+
width: width * cos + height * sin,
|
|
9160
|
+
height: sin * width + cos * height
|
|
9161
|
+
};
|
|
9162
|
+
}
|
|
9163
|
+
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
9164
|
+
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
9165
|
+
}
|
|
9166
|
+
function computeCachedTextWidth(context, text, font, rotation) {
|
|
9167
|
+
const size = computeCachedTextDimension(context, text, font);
|
|
9168
|
+
if (!rotation) return size.width;
|
|
9169
|
+
const cos = Math.abs(Math.cos(rotation));
|
|
9170
|
+
const sin = Math.abs(Math.sin(rotation));
|
|
9171
|
+
return size.width * cos + size.height * sin;
|
|
9172
|
+
}
|
|
9173
|
+
const textDimensionsCache = {};
|
|
9174
|
+
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
9175
|
+
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
9176
|
+
if (!style.rotation) return size;
|
|
9177
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9178
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9179
|
+
return {
|
|
9180
|
+
width: size.width * cos + size.height * sin,
|
|
9181
|
+
height: size.height * cos + size.width * sin
|
|
9182
|
+
};
|
|
9183
|
+
}
|
|
9184
|
+
function computeCachedTextDimension(context, text, font) {
|
|
9185
|
+
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
9186
|
+
if (textDimensionsCache[font][text] === void 0) {
|
|
9187
|
+
context.save();
|
|
9188
|
+
context.font = font;
|
|
9189
|
+
const measure = context.measureText(text);
|
|
9190
|
+
context.restore();
|
|
9191
|
+
const width = measure.width;
|
|
9192
|
+
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9193
|
+
textDimensionsCache[font][text] = {
|
|
9194
|
+
width,
|
|
9195
|
+
height
|
|
9196
|
+
};
|
|
9197
|
+
}
|
|
9198
|
+
return textDimensionsCache[font][text];
|
|
9199
|
+
}
|
|
9200
|
+
function fontSizeInPixels(fontSize) {
|
|
9201
|
+
return Math.round(fontSize * 96 / 72);
|
|
9202
|
+
}
|
|
9203
|
+
function computeTextFont(style, fontUnit = "pt") {
|
|
9204
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9205
|
+
}
|
|
9206
|
+
function computeTextFontSizeInPixels(style) {
|
|
9207
|
+
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
9208
|
+
}
|
|
9209
|
+
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
9210
|
+
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
9211
|
+
const splitWord = [];
|
|
9212
|
+
let wordPart = "";
|
|
9213
|
+
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
9214
|
+
splitWord.push(wordPart);
|
|
9215
|
+
wordPart = l;
|
|
9216
|
+
} else wordPart += l;
|
|
9217
|
+
splitWord.push(wordPart);
|
|
9218
|
+
return splitWord;
|
|
9219
|
+
}
|
|
9220
|
+
/**
|
|
9221
|
+
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
9222
|
+
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
9223
|
+
*/
|
|
9224
|
+
function splitTextToWidth(ctx, text, style, width) {
|
|
9225
|
+
if (!style) style = {};
|
|
9226
|
+
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
9227
|
+
const brokenText = [];
|
|
9228
|
+
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
9229
|
+
for (const line of lines) {
|
|
9230
|
+
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
9231
|
+
if (!width) {
|
|
9232
|
+
brokenText.push(line);
|
|
9233
|
+
continue;
|
|
9234
|
+
}
|
|
9235
|
+
let textLine = "";
|
|
9236
|
+
let availableWidth = width;
|
|
9237
|
+
for (const word of words) {
|
|
9238
|
+
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
9239
|
+
const lastPart = splitWord.pop();
|
|
9240
|
+
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
9241
|
+
if (splitWord.length) {
|
|
9242
|
+
if (textLine !== "") {
|
|
9243
|
+
brokenText.push(textLine);
|
|
9244
|
+
textLine = "";
|
|
9245
|
+
availableWidth = width;
|
|
9246
|
+
}
|
|
9247
|
+
splitWord.forEach((wordPart) => {
|
|
9248
|
+
brokenText.push(wordPart);
|
|
9249
|
+
});
|
|
9250
|
+
textLine = lastPart;
|
|
9251
|
+
availableWidth = width - lastPartWidth;
|
|
9252
|
+
} else {
|
|
9253
|
+
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
9254
|
+
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
9255
|
+
if (wordWidth <= availableWidth) {
|
|
9256
|
+
textLine += _word;
|
|
9257
|
+
availableWidth -= wordWidth;
|
|
9258
|
+
} else {
|
|
9259
|
+
brokenText.push(textLine);
|
|
9260
|
+
textLine = lastPart;
|
|
9261
|
+
availableWidth = width - lastPartWidth;
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
if (textLine !== "") brokenText.push(textLine);
|
|
9266
|
+
}
|
|
9267
|
+
return brokenText;
|
|
9268
|
+
}
|
|
9269
|
+
/**
|
|
9270
|
+
* Return the font size that makes the width of a text match the given line width.
|
|
9271
|
+
* Minimum font size is 1.
|
|
9272
|
+
*
|
|
9273
|
+
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
9274
|
+
*/
|
|
9275
|
+
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
9276
|
+
let minFontSize = 1;
|
|
9277
|
+
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
9278
|
+
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
9279
|
+
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
9280
|
+
let currentTextWidth = getTextWidth(fontSize);
|
|
9281
|
+
let iterations = 0;
|
|
9282
|
+
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
9283
|
+
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
9284
|
+
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
9285
|
+
fontSize = (minFontSize + maxFontSize) / 2;
|
|
9286
|
+
currentTextWidth = getTextWidth(fontSize);
|
|
9287
|
+
iterations++;
|
|
9288
|
+
}
|
|
9289
|
+
return fontSize;
|
|
9290
|
+
}
|
|
9291
|
+
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
9292
|
+
function toTrimmedLowerCase(str) {
|
|
9293
|
+
return str ? str.toLowerCase().trim() : "";
|
|
9505
9294
|
}
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9295
|
+
/**
|
|
9296
|
+
* Extract the fontSize from a context font string
|
|
9297
|
+
* @param font The (context) font string to parse
|
|
9298
|
+
* @returns The fontSize in pixels
|
|
9299
|
+
*/
|
|
9300
|
+
const pxRegex = /([0-9\.]*)px/;
|
|
9301
|
+
function getContextFontSize(font) {
|
|
9302
|
+
return Number(font.match(pxRegex)?.[1]);
|
|
9511
9303
|
}
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9304
|
+
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
9305
|
+
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9306
|
+
if (width <= maxWidth) return text;
|
|
9307
|
+
const ellipsis = "…";
|
|
9308
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
9309
|
+
if (width <= ellipsisWidth) return text;
|
|
9310
|
+
let len = text.length;
|
|
9311
|
+
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
9312
|
+
text = text.substring(0, len);
|
|
9313
|
+
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9519
9314
|
}
|
|
9520
|
-
|
|
9521
|
-
|
|
9315
|
+
return text + ellipsis;
|
|
9316
|
+
}
|
|
9317
|
+
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
|
|
9318
|
+
if (highlightText) {
|
|
9319
|
+
context.save();
|
|
9320
|
+
context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
|
|
9522
9321
|
}
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9322
|
+
context.fillText(text, position.x, position.y);
|
|
9323
|
+
if (!underline && !strikethrough) return;
|
|
9324
|
+
const measure = context.measureText(text);
|
|
9325
|
+
const textWidth = measure.width;
|
|
9326
|
+
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
9327
|
+
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9328
|
+
let { x, y } = position;
|
|
9329
|
+
let strikeY = y, underlineY = y;
|
|
9330
|
+
switch (context.textAlign) {
|
|
9331
|
+
case "center":
|
|
9332
|
+
x -= textWidth / 2;
|
|
9333
|
+
break;
|
|
9334
|
+
case "right":
|
|
9335
|
+
x -= textWidth;
|
|
9336
|
+
break;
|
|
9528
9337
|
}
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9338
|
+
switch (context.textBaseline) {
|
|
9339
|
+
case "top":
|
|
9340
|
+
underlineY += boxHeight - 2 * strokeWidth;
|
|
9341
|
+
strikeY += boxHeight / 2 - strokeWidth;
|
|
9342
|
+
break;
|
|
9343
|
+
case "middle":
|
|
9344
|
+
underlineY += boxHeight / 2 - strokeWidth;
|
|
9345
|
+
break;
|
|
9346
|
+
case "alphabetic":
|
|
9347
|
+
underlineY += 2 * strokeWidth;
|
|
9348
|
+
strikeY -= 3 * strokeWidth;
|
|
9349
|
+
break;
|
|
9350
|
+
case "bottom":
|
|
9351
|
+
underlineY = y;
|
|
9352
|
+
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
9353
|
+
break;
|
|
9535
9354
|
}
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9355
|
+
if (underline) {
|
|
9356
|
+
context.lineWidth = strokeWidth;
|
|
9357
|
+
context.strokeStyle = context.fillStyle;
|
|
9358
|
+
context.beginPath();
|
|
9359
|
+
context.moveTo(x, underlineY);
|
|
9360
|
+
context.lineTo(x + textWidth, underlineY);
|
|
9361
|
+
context.stroke();
|
|
9539
9362
|
}
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
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);
|
|
9363
|
+
if (strikethrough) {
|
|
9364
|
+
context.lineWidth = strokeWidth;
|
|
9365
|
+
context.strokeStyle = context.fillStyle;
|
|
9366
|
+
context.beginPath();
|
|
9367
|
+
context.moveTo(x, strikeY);
|
|
9368
|
+
context.lineTo(x + textWidth, strikeY);
|
|
9369
|
+
context.stroke();
|
|
9370
|
+
}
|
|
9371
|
+
if (highlightText) context.restore();
|
|
9372
|
+
}
|
|
9373
|
+
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
9374
|
+
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
9375
|
+
const ellipsis = "...";
|
|
9376
|
+
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
9377
|
+
if (ellipsisWidth >= width) return "";
|
|
9378
|
+
let lowerBoundLen = 1;
|
|
9379
|
+
let upperBoundLen = text.length;
|
|
9380
|
+
let currentWidth;
|
|
9381
|
+
while (lowerBoundLen <= upperBoundLen) {
|
|
9382
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
9383
|
+
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
9384
|
+
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
9385
|
+
else lowerBoundLen = currentLen + 1;
|
|
9386
|
+
}
|
|
9387
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
9388
|
+
return slicedText ? slicedText + ellipsis : "";
|
|
9389
|
+
}
|
|
9589
9390
|
/**
|
|
9590
|
-
*
|
|
9591
|
-
*
|
|
9391
|
+
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
9392
|
+
* with to original's text vertical and horizontal alignment.
|
|
9592
9393
|
*/
|
|
9593
|
-
function
|
|
9594
|
-
if (
|
|
9595
|
-
|
|
9596
|
-
const
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9394
|
+
function computeRotationPosition(rect, style) {
|
|
9395
|
+
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
9396
|
+
let { x, y } = rect;
|
|
9397
|
+
const cos = Math.cos(-style.rotation);
|
|
9398
|
+
const sin = Math.sin(-style.rotation);
|
|
9399
|
+
const width = rect.textWidth - 2 * 4;
|
|
9400
|
+
const height = rect.textHeight;
|
|
9401
|
+
const center = style.align === "center";
|
|
9402
|
+
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
9403
|
+
const sh = sin * height;
|
|
9404
|
+
const sw = Math.abs(sin * width);
|
|
9405
|
+
const ch = cos * height;
|
|
9406
|
+
if (style.verticalAlign === "top") if (center) {
|
|
9407
|
+
y += sw / 2;
|
|
9408
|
+
x -= sh / 2;
|
|
9409
|
+
} else if (rotateTowardCellCenter) x -= sh;
|
|
9410
|
+
else y += sw;
|
|
9411
|
+
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
9412
|
+
y += height - ch;
|
|
9413
|
+
if (center) {
|
|
9414
|
+
y -= sw / 2;
|
|
9415
|
+
x -= sh / 2;
|
|
9416
|
+
} else if (rotateTowardCellCenter) {
|
|
9417
|
+
x -= sh;
|
|
9418
|
+
y -= sw;
|
|
9419
|
+
}
|
|
9420
|
+
} else if (center) {
|
|
9421
|
+
x -= sh / 2;
|
|
9422
|
+
y -= height / 2;
|
|
9423
|
+
if (rotateTowardCellCenter) y += sh;
|
|
9424
|
+
else y -= sh;
|
|
9425
|
+
} else if (rotateTowardCellCenter) {
|
|
9426
|
+
x -= sh;
|
|
9427
|
+
y -= sw / 2;
|
|
9428
|
+
} else y += sw / 2 + ch / 4;
|
|
9429
|
+
return {
|
|
9430
|
+
x: cos * x - sin * y,
|
|
9431
|
+
y: cos * y + sin * x
|
|
9432
|
+
};
|
|
9433
|
+
}
|
|
9434
|
+
|
|
9435
|
+
//#endregion
|
|
9436
|
+
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
9437
|
+
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
9438
|
+
const chartColorScalePlugin = {
|
|
9439
|
+
id: "chartColorScalePlugin",
|
|
9440
|
+
afterDatasetsDraw(chart, args, options) {
|
|
9441
|
+
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
9442
|
+
const ctx = chart.ctx;
|
|
9443
|
+
ctx.save();
|
|
9444
|
+
ctx.textAlign = "center";
|
|
9445
|
+
ctx.textBaseline = "middle";
|
|
9446
|
+
ctx.miterLimit = 1;
|
|
9447
|
+
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
9448
|
+
const gradientWidth = 10;
|
|
9449
|
+
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
9450
|
+
const gradientY = chart.chartArea.top;
|
|
9451
|
+
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
9452
|
+
const step = 1 / (options.colorScale.length - 1);
|
|
9453
|
+
options.colorScale.forEach((color, index) => {
|
|
9454
|
+
gradient.addColorStop(index * step, color);
|
|
9610
9455
|
});
|
|
9456
|
+
ctx.fillStyle = gradient;
|
|
9457
|
+
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
9458
|
+
ctx.fillStyle = options.fontColor ?? "black";
|
|
9459
|
+
ctx.font = getDefaultContextFont(12);
|
|
9460
|
+
ctx.textAlign = "left";
|
|
9461
|
+
let minValue = Math.round(options.minValue * 100) / 100;
|
|
9462
|
+
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
9463
|
+
if (options.minValue === options.maxValue) {
|
|
9464
|
+
minValue -= 1;
|
|
9465
|
+
maxValue += 1;
|
|
9466
|
+
}
|
|
9467
|
+
const formattedMaxValue = humanizeNumber({
|
|
9468
|
+
value: maxValue,
|
|
9469
|
+
format: void 0
|
|
9470
|
+
}, options.locale);
|
|
9471
|
+
const formattedMinValue = humanizeNumber({
|
|
9472
|
+
value: minValue,
|
|
9473
|
+
format: void 0
|
|
9474
|
+
}, options.locale);
|
|
9475
|
+
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
9476
|
+
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
9477
|
+
ctx.restore();
|
|
9611
9478
|
}
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9479
|
+
};
|
|
9480
|
+
|
|
9481
|
+
//#endregion
|
|
9482
|
+
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
9483
|
+
function getFunnelChartController() {
|
|
9484
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9485
|
+
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
9486
|
+
static id = "funnel";
|
|
9487
|
+
static defaults = {
|
|
9488
|
+
...globalThis.Chart?.BarController.defaults,
|
|
9489
|
+
dataElementType: "funnel",
|
|
9490
|
+
animation: { duration: (ctx) => {
|
|
9491
|
+
if (ctx.type !== "data") return 1e3;
|
|
9492
|
+
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
9493
|
+
} }
|
|
9494
|
+
};
|
|
9495
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9496
|
+
updateElements(rects, start, count, mode) {
|
|
9497
|
+
super.updateElements(rects, start, count, mode);
|
|
9498
|
+
for (let i = start; i < start + count; i++) {
|
|
9499
|
+
const rect = rects[i];
|
|
9500
|
+
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9616
9503
|
};
|
|
9617
9504
|
}
|
|
9618
|
-
function
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9505
|
+
function getFunnelChartElement() {
|
|
9506
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9507
|
+
/**
|
|
9508
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9509
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9510
|
+
*/
|
|
9511
|
+
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
9512
|
+
static id = "funnel";
|
|
9513
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9514
|
+
draw(ctx) {
|
|
9515
|
+
ctx.save();
|
|
9516
|
+
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
9517
|
+
"x",
|
|
9518
|
+
"y",
|
|
9519
|
+
"width",
|
|
9520
|
+
"height",
|
|
9521
|
+
"nextElement",
|
|
9522
|
+
"base",
|
|
9523
|
+
"options"
|
|
9524
|
+
]);
|
|
9525
|
+
const width = getElementWidth(this);
|
|
9526
|
+
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
9527
|
+
const startX = Math.min(x, base);
|
|
9528
|
+
const startY = y - height / 2;
|
|
9529
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9530
|
+
ctx.beginPath();
|
|
9531
|
+
ctx.moveTo(startX, startY);
|
|
9532
|
+
ctx.lineTo(startX + width, startY);
|
|
9533
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9534
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9535
|
+
ctx.closePath();
|
|
9536
|
+
ctx.fill();
|
|
9537
|
+
if (options.borderWidth) {
|
|
9538
|
+
ctx.strokeStyle = options.borderColor;
|
|
9539
|
+
ctx.lineWidth = options.borderWidth;
|
|
9540
|
+
ctx.stroke();
|
|
9541
|
+
}
|
|
9542
|
+
ctx.restore();
|
|
9543
|
+
}
|
|
9544
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9545
|
+
inRange(mouseX, mouseY) {
|
|
9546
|
+
const { x, y, height, nextElement, base } = this.getProps([
|
|
9547
|
+
"x",
|
|
9548
|
+
"y",
|
|
9549
|
+
"width",
|
|
9550
|
+
"height",
|
|
9551
|
+
"nextElement",
|
|
9552
|
+
"base",
|
|
9553
|
+
"options"
|
|
9554
|
+
]);
|
|
9555
|
+
const width = getElementWidth(this);
|
|
9556
|
+
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
9557
|
+
const startX = Math.min(x, base);
|
|
9558
|
+
const startY = y - height / 2;
|
|
9559
|
+
if (mouseY < startY || mouseY > startY + height) return false;
|
|
9560
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9561
|
+
const left = startX + offset * (mouseY - startY) / height;
|
|
9562
|
+
const right = startX + width - offset * (mouseY - startY) / height;
|
|
9563
|
+
if (mouseX < left || mouseX > right) return false;
|
|
9564
|
+
return true;
|
|
9565
|
+
}
|
|
9566
|
+
};
|
|
9628
9567
|
}
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9568
|
+
/**
|
|
9569
|
+
* Get an element width.
|
|
9570
|
+
*
|
|
9571
|
+
* The property width is undefined during animations, we need to compute it manually.
|
|
9572
|
+
*/
|
|
9573
|
+
function getElementWidth(element) {
|
|
9574
|
+
const { x, base } = element.getProps(["x", "base"]);
|
|
9575
|
+
return Math.max(x, base) - Math.min(x, base);
|
|
9635
9576
|
}
|
|
9577
|
+
/**
|
|
9578
|
+
* Position the tooltip inside the trapezoid.
|
|
9579
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9580
|
+
*/
|
|
9581
|
+
const funnelTooltipPositioner = function(elements) {
|
|
9582
|
+
if (!elements.length) return {
|
|
9583
|
+
x: 0,
|
|
9584
|
+
y: 0
|
|
9585
|
+
};
|
|
9586
|
+
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
9587
|
+
"x",
|
|
9588
|
+
"y",
|
|
9589
|
+
"width",
|
|
9590
|
+
"height",
|
|
9591
|
+
"base"
|
|
9592
|
+
]);
|
|
9593
|
+
const startX = Math.min(x, base);
|
|
9594
|
+
const startY = y - height / 2;
|
|
9595
|
+
return {
|
|
9596
|
+
x: startX + width * 2 / 3,
|
|
9597
|
+
y: startY + height / 2
|
|
9598
|
+
};
|
|
9599
|
+
};
|
|
9600
|
+
|
|
9601
|
+
//#endregion
|
|
9602
|
+
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
9603
|
+
const chartMinorGridPlugin = {
|
|
9604
|
+
id: "o-spreadsheet-minor-gridlines",
|
|
9605
|
+
beforeDatasetsDraw(chart) {
|
|
9606
|
+
const ctx = chart.ctx;
|
|
9607
|
+
const chartArea = chart.chartArea;
|
|
9608
|
+
if (!chartArea) return;
|
|
9609
|
+
for (const scaleId in chart.scales) {
|
|
9610
|
+
const scale = chart.scales[scaleId];
|
|
9611
|
+
const options = scale.options;
|
|
9612
|
+
const minor = options?.grid?.minor;
|
|
9613
|
+
if (!minor?.display) continue;
|
|
9614
|
+
const showMajorGrid = options?.grid?.display;
|
|
9615
|
+
const ticks = scale.ticks;
|
|
9616
|
+
if (!ticks || ticks.length < 2) continue;
|
|
9617
|
+
ctx.save();
|
|
9618
|
+
ctx.lineWidth = 1;
|
|
9619
|
+
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
9620
|
+
for (let i = 0; i < ticks.length - 1; i++) {
|
|
9621
|
+
const start = scale.getPixelForTick(i);
|
|
9622
|
+
const end = scale.getPixelForTick(i + 1);
|
|
9623
|
+
if (!isFinite(start) || !isFinite(end)) continue;
|
|
9624
|
+
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
9625
|
+
const ratio = j / 4;
|
|
9626
|
+
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
9627
|
+
ctx.beginPath();
|
|
9628
|
+
if (scale.isHorizontal()) {
|
|
9629
|
+
ctx.moveTo(position, chartArea.top);
|
|
9630
|
+
ctx.lineTo(position, chartArea.bottom);
|
|
9631
|
+
} else {
|
|
9632
|
+
ctx.moveTo(chartArea.left, position);
|
|
9633
|
+
ctx.lineTo(chartArea.right, position);
|
|
9634
|
+
}
|
|
9635
|
+
ctx.stroke();
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
ctx.restore();
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9641
|
+
};
|
|
9636
9642
|
|
|
9637
9643
|
//#endregion
|
|
9638
9644
|
//#region src/xlsx/constants.ts
|
|
@@ -12072,11 +12078,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12072
12078
|
if (structure.baseline) {
|
|
12073
12079
|
ctx.font = structure.baseline.style.font;
|
|
12074
12080
|
ctx.fillStyle = structure.baseline.style.color;
|
|
12075
|
-
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
|
|
12081
|
+
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
|
|
12076
12082
|
}
|
|
12077
12083
|
if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
|
|
12078
12084
|
ctx.save();
|
|
12079
|
-
ctx.fillStyle = structure.baselineArrow.style.color;
|
|
12085
|
+
ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
|
|
12080
12086
|
ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
|
|
12081
12087
|
const ratio = structure.baselineArrow.style.size / 10;
|
|
12082
12088
|
ctx.scale(ratio, ratio);
|
|
@@ -12094,18 +12100,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12094
12100
|
const descr = structure.baselineDescr;
|
|
12095
12101
|
ctx.font = descr.style.font;
|
|
12096
12102
|
ctx.fillStyle = descr.style.color;
|
|
12097
|
-
ctx
|
|
12103
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
|
|
12098
12104
|
}
|
|
12099
12105
|
if (structure.key) {
|
|
12100
12106
|
ctx.font = structure.key.style.font;
|
|
12101
12107
|
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);
|
|
12108
|
+
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
12109
|
}
|
|
12104
12110
|
if (structure.keyDescr) {
|
|
12105
12111
|
const descr = structure.keyDescr;
|
|
12106
12112
|
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12107
12113
|
ctx.fillStyle = descr.style.color;
|
|
12108
|
-
ctx
|
|
12114
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
|
|
12109
12115
|
}
|
|
12110
12116
|
if (structure.progressBar) {
|
|
12111
12117
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
@@ -12329,29 +12335,34 @@ var ScorecardChartConfigBuilder = class {
|
|
|
12329
12335
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12330
12336
|
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12331
12337
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12332
|
-
underline: this.runtime.keyValueStyle?.underline
|
|
12338
|
+
underline: this.runtime.keyValueStyle?.underline,
|
|
12339
|
+
highlightText: this.runtime.keyHighlight
|
|
12333
12340
|
},
|
|
12334
12341
|
keyDescr: {
|
|
12335
12342
|
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12336
12343
|
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12337
12344
|
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12338
|
-
underline: this.runtime.keyValueDescrStyle?.underline
|
|
12345
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12346
|
+
highlightText: this.runtime.keyHighlight
|
|
12339
12347
|
},
|
|
12340
12348
|
baselineValue: {
|
|
12341
12349
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12342
12350
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12343
12351
|
underline: this.runtime.baselineStyle?.underline,
|
|
12344
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12352
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12353
|
+
highlightText: this.runtime.baselineHighlight
|
|
12345
12354
|
},
|
|
12346
12355
|
baselineDescr: {
|
|
12347
12356
|
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12348
12357
|
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12349
12358
|
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12350
|
-
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
|
|
12359
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12360
|
+
highlightText: this.runtime.baselineHighlight
|
|
12351
12361
|
},
|
|
12352
12362
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
|
|
12353
12363
|
size: this.keyValue ? .8 * baselineValueFontSize : 0,
|
|
12354
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12364
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12365
|
+
highlight: this.runtime.baselineHighlight
|
|
12355
12366
|
}
|
|
12356
12367
|
};
|
|
12357
12368
|
}
|
|
@@ -12447,11 +12458,14 @@ var ScorecardChart = class extends Component {
|
|
|
12447
12458
|
});
|
|
12448
12459
|
onWillUnmount(() => resizeObserver.disconnect());
|
|
12449
12460
|
}
|
|
12461
|
+
config(canvasRect, zoom) {
|
|
12462
|
+
return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
|
|
12463
|
+
}
|
|
12450
12464
|
createChart() {
|
|
12451
12465
|
const canvas = this.canvas();
|
|
12452
12466
|
if (!canvas) return;
|
|
12453
12467
|
const zoom = this.env.model.getters.getViewportZoomLevel();
|
|
12454
|
-
drawScoreChart(
|
|
12468
|
+
drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
|
|
12455
12469
|
}
|
|
12456
12470
|
};
|
|
12457
12471
|
|
|
@@ -21791,10 +21805,10 @@ function toNormalizedPivotValue(dimension, groupValue) {
|
|
|
21791
21805
|
type: dimension.type
|
|
21792
21806
|
}));
|
|
21793
21807
|
if (groupValueString.toLowerCase() === "false") return false;
|
|
21794
|
-
return pivotNormalizationValueRegistry.get(dimension.type)(groupValueString, dimension
|
|
21808
|
+
return pivotNormalizationValueRegistry.get(dimension.type)(groupValueString, dimension);
|
|
21795
21809
|
}
|
|
21796
|
-
function normalizeDateTime(value,
|
|
21797
|
-
return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
|
|
21810
|
+
function normalizeDateTime(value, dimension) {
|
|
21811
|
+
return pivotTimeAdapter(dimension.granularity ?? "month").normalizeFunctionValue(value);
|
|
21798
21812
|
}
|
|
21799
21813
|
function toFunctionPivotValue(value, dimension) {
|
|
21800
21814
|
if (value === null) return `"null"`;
|
|
@@ -28055,7 +28069,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
|
|
|
28055
28069
|
*/
|
|
28056
28070
|
function filterInvalidHierarchicalPoints(values, hierarchy) {
|
|
28057
28071
|
const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
|
|
28058
|
-
const isEmpty = (value) => value === null || value === "";
|
|
28072
|
+
const isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
28059
28073
|
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
|
|
28060
28074
|
const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
|
|
28061
28075
|
if (isEmpty(groups[0]?.value)) return false;
|
|
@@ -34536,6 +34550,35 @@ var CellIsRuleEditor = class extends Component {
|
|
|
34536
34550
|
}
|
|
34537
34551
|
};
|
|
34538
34552
|
|
|
34553
|
+
//#endregion
|
|
34554
|
+
//#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
|
|
34555
|
+
var CalendarButton = class extends Component {
|
|
34556
|
+
static template = "o-spreadsheet-CalendarButton";
|
|
34557
|
+
props = props({
|
|
34558
|
+
value: types$1.string().optional(""),
|
|
34559
|
+
onChange: types$1.function()
|
|
34560
|
+
});
|
|
34561
|
+
datePickerRef = signal.ref(HTMLInputElement);
|
|
34562
|
+
openCalendar() {
|
|
34563
|
+
this.datePickerRef()?.showPicker();
|
|
34564
|
+
}
|
|
34565
|
+
formatDateForInput(value) {
|
|
34566
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34567
|
+
return dateValue ? formatValue(dateValue.value, {
|
|
34568
|
+
format: "yyyy-mm-dd",
|
|
34569
|
+
locale: DEFAULT_LOCALE
|
|
34570
|
+
}) : "";
|
|
34571
|
+
}
|
|
34572
|
+
onDateInputValueChanged(value) {
|
|
34573
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34574
|
+
const formattedValue = dateValue ? formatValue(dateValue.value, {
|
|
34575
|
+
format: DEFAULT_LOCALE.dateFormat,
|
|
34576
|
+
locale: DEFAULT_LOCALE
|
|
34577
|
+
}) : "";
|
|
34578
|
+
this.props.onChange(formattedValue);
|
|
34579
|
+
}
|
|
34580
|
+
};
|
|
34581
|
+
|
|
34539
34582
|
//#endregion
|
|
34540
34583
|
//#region src/components/side_panel/criterion_form/criterion_form.ts
|
|
34541
34584
|
var CriterionForm = class extends Component {
|
|
@@ -35224,7 +35267,8 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35224
35267
|
static template = "o-spreadsheet-DataValidationDateCriterion";
|
|
35225
35268
|
static components = {
|
|
35226
35269
|
CriterionInput,
|
|
35227
|
-
Select
|
|
35270
|
+
Select,
|
|
35271
|
+
CalendarButton
|
|
35228
35272
|
};
|
|
35229
35273
|
get currentDateValue() {
|
|
35230
35274
|
return this.props.criterion.dateValue || "exactDate";
|
|
@@ -35250,7 +35294,10 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35250
35294
|
//#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
|
|
35251
35295
|
var DoubleInputCriterionForm = class extends CriterionForm {
|
|
35252
35296
|
static template = "o-spreadsheet-DoubleInputCriterionForm";
|
|
35253
|
-
static components = {
|
|
35297
|
+
static components = {
|
|
35298
|
+
CriterionInput,
|
|
35299
|
+
CalendarButton
|
|
35300
|
+
};
|
|
35254
35301
|
onFirstValueChanged(value) {
|
|
35255
35302
|
const values = this.props.criterion.values;
|
|
35256
35303
|
this.updateCriterion({ values: [value, values[1] || ""] });
|
|
@@ -35259,6 +35306,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
|
|
|
35259
35306
|
const values = this.props.criterion.values;
|
|
35260
35307
|
this.updateCriterion({ values: [values[0] || "", value] });
|
|
35261
35308
|
}
|
|
35309
|
+
get isDateType() {
|
|
35310
|
+
return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
|
|
35311
|
+
}
|
|
35262
35312
|
};
|
|
35263
35313
|
|
|
35264
35314
|
//#endregion
|
|
@@ -46605,7 +46655,7 @@ var FiguresContainer = class extends Component {
|
|
|
46605
46655
|
horizontalSnap: void 0,
|
|
46606
46656
|
verticalSnap: void 0,
|
|
46607
46657
|
cancelDnd: void 0,
|
|
46608
|
-
|
|
46658
|
+
overlappingChartOrCarousel: void 0
|
|
46609
46659
|
});
|
|
46610
46660
|
setup() {
|
|
46611
46661
|
onMounted(() => {
|
|
@@ -46621,7 +46671,7 @@ var FiguresContainer = class extends Component {
|
|
|
46621
46671
|
this.dnd.selectedRect = void 0;
|
|
46622
46672
|
this.dnd.horizontalSnap = void 0;
|
|
46623
46673
|
this.dnd.verticalSnap = void 0;
|
|
46624
|
-
this.dnd.
|
|
46674
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46625
46675
|
this.dnd.cancelDnd = void 0;
|
|
46626
46676
|
}
|
|
46627
46677
|
});
|
|
@@ -46759,11 +46809,11 @@ var FiguresContainer = class extends Component {
|
|
|
46759
46809
|
hasStartedDnd = true;
|
|
46760
46810
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
|
|
46761
46811
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
46762
|
-
let
|
|
46812
|
+
let overlappingChartOrCarousel = void 0;
|
|
46763
46813
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
46764
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart"))
|
|
46765
|
-
this.dnd.
|
|
46766
|
-
if (!
|
|
46814
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
|
|
46815
|
+
this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
|
|
46816
|
+
if (!overlappingChartOrCarousel) {
|
|
46767
46817
|
const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
|
|
46768
46818
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
46769
46819
|
this.dnd.selectedRect = this.getDndFigureRect();
|
|
@@ -46784,7 +46834,7 @@ var FiguresContainer = class extends Component {
|
|
|
46784
46834
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
46785
46835
|
return;
|
|
46786
46836
|
}
|
|
46787
|
-
if (!this.dnd.
|
|
46837
|
+
if (!this.dnd.overlappingChartOrCarousel) {
|
|
46788
46838
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
46789
46839
|
return {
|
|
46790
46840
|
sheetId,
|
|
@@ -46794,20 +46844,25 @@ var FiguresContainer = class extends Component {
|
|
|
46794
46844
|
}) || [];
|
|
46795
46845
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
46796
46846
|
} else {
|
|
46797
|
-
const
|
|
46847
|
+
const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
|
|
46798
46848
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
46799
|
-
this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46849
|
+
if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46800
46850
|
sheetId,
|
|
46801
|
-
carouselFigureId,
|
|
46851
|
+
carouselFigureId: overlappingFigureId,
|
|
46802
46852
|
chartFigureIds
|
|
46803
46853
|
});
|
|
46854
|
+
else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
46855
|
+
sheetId,
|
|
46856
|
+
baseFigureId: overlappingFigureId,
|
|
46857
|
+
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
46858
|
+
});
|
|
46804
46859
|
}
|
|
46805
46860
|
this.dnd.draggedFigure = void 0;
|
|
46806
46861
|
this.dnd.selectedFigures = void 0;
|
|
46807
46862
|
this.dnd.selectedRect = void 0;
|
|
46808
46863
|
this.dnd.horizontalSnap = void 0;
|
|
46809
46864
|
this.dnd.verticalSnap = void 0;
|
|
46810
|
-
this.dnd.
|
|
46865
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46811
46866
|
};
|
|
46812
46867
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46813
46868
|
}
|
|
@@ -46863,7 +46918,7 @@ var FiguresContainer = class extends Component {
|
|
|
46863
46918
|
this.dnd.selectedRect = void 0;
|
|
46864
46919
|
this.dnd.horizontalSnap = void 0;
|
|
46865
46920
|
this.dnd.verticalSnap = void 0;
|
|
46866
|
-
this.dnd.
|
|
46921
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46867
46922
|
};
|
|
46868
46923
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46869
46924
|
}
|
|
@@ -46873,12 +46928,12 @@ var FiguresContainer = class extends Component {
|
|
|
46873
46928
|
getFigureStyle(figureUI) {
|
|
46874
46929
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
46875
46930
|
return cssPropertiesToCss({
|
|
46876
|
-
opacity: this.dnd.
|
|
46931
|
+
opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
|
|
46877
46932
|
cursor: "grabbing"
|
|
46878
46933
|
});
|
|
46879
46934
|
}
|
|
46880
46935
|
getFigureClass(figureUI) {
|
|
46881
|
-
if (figureUI.id !== this.dnd.
|
|
46936
|
+
if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
|
|
46882
46937
|
return "o-add-to-carousel";
|
|
46883
46938
|
}
|
|
46884
46939
|
getSnap(snapLine) {
|
|
@@ -46921,18 +46976,18 @@ var FiguresContainer = class extends Component {
|
|
|
46921
46976
|
height: `100%`
|
|
46922
46977
|
});
|
|
46923
46978
|
}
|
|
46924
|
-
|
|
46979
|
+
getOverlappingFigure(figureUI, otherFigures) {
|
|
46925
46980
|
if (figureUI.tag !== "chart") return;
|
|
46926
46981
|
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
46927
46982
|
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
46928
46983
|
let bestMatch;
|
|
46929
46984
|
let smallestDistance = Infinity;
|
|
46930
46985
|
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 -
|
|
46986
|
+
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
46987
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
46988
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
46989
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
46990
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
46936
46991
|
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
46937
46992
|
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
46938
46993
|
smallestDistance = squaredDistance;
|
|
@@ -58719,13 +58774,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
|
|
|
58719
58774
|
const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
|
|
58720
58775
|
const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
|
|
58721
58776
|
const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
|
|
58722
|
-
if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58777
|
+
if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58723
58778
|
const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
|
|
58724
58779
|
const colorThresholds = [{
|
|
58725
58780
|
value: minValue,
|
|
58726
58781
|
color: rule.minimum.color
|
|
58727
58782
|
}];
|
|
58728
|
-
if (rule.midpoint && midValue) colorThresholds.push({
|
|
58783
|
+
if (rule.midpoint && midValue !== null) colorThresholds.push({
|
|
58729
58784
|
value: midValue,
|
|
58730
58785
|
color: rule.midpoint.color
|
|
58731
58786
|
});
|
|
@@ -66184,7 +66239,9 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66184
66239
|
ctx.lineWidth = 1.5 * thinLineWidth;
|
|
66185
66240
|
const isDarkMode = this.getters.isDarkMode();
|
|
66186
66241
|
for (const zone of zones) {
|
|
66242
|
+
if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
|
|
66187
66243
|
const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
|
|
66244
|
+
const currentLineWidth = ctx.lineWidth;
|
|
66188
66245
|
if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
|
|
66189
66246
|
if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
|
|
66190
66247
|
if (height === 0 && width === 0) {
|
|
@@ -66198,6 +66255,7 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66198
66255
|
ctx.globalCompositeOperation = "source-over";
|
|
66199
66256
|
ctx.strokeRect(x, y, width, height);
|
|
66200
66257
|
}
|
|
66258
|
+
ctx.lineWidth = currentLineWidth;
|
|
66201
66259
|
}
|
|
66202
66260
|
ctx.globalCompositeOperation = "source-over";
|
|
66203
66261
|
const position = renderingContext.activePosition;
|
|
@@ -66344,6 +66402,9 @@ var InternalViewport = class {
|
|
|
66344
66402
|
this.adjustViewportZoneX();
|
|
66345
66403
|
this.adjustViewportZoneY();
|
|
66346
66404
|
}
|
|
66405
|
+
isZoneVisible(zone) {
|
|
66406
|
+
return intersection(zone, this) !== void 0;
|
|
66407
|
+
}
|
|
66347
66408
|
/**
|
|
66348
66409
|
*
|
|
66349
66410
|
* Computes the visible coordinates & dimensions of a given zone inside the viewport
|
|
@@ -66624,6 +66685,9 @@ var ViewportCollection = class {
|
|
|
66624
66685
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
66625
66686
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
66626
66687
|
}
|
|
66688
|
+
isZoneVisibleInViewport(sheetId, zone) {
|
|
66689
|
+
return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
|
|
66690
|
+
}
|
|
66627
66691
|
getScrollBarWidth() {
|
|
66628
66692
|
return 15 / this.zoomLevel;
|
|
66629
66693
|
}
|
|
@@ -70697,18 +70761,28 @@ var NamedRangeSelector = class extends Component {
|
|
|
70697
70761
|
return;
|
|
70698
70762
|
}
|
|
70699
70763
|
const activeSheetId = this.env.model.getters.getActiveSheetId();
|
|
70700
|
-
if (activeSheetId !== sheetId)
|
|
70701
|
-
|
|
70702
|
-
|
|
70703
|
-
|
|
70704
|
-
|
|
70764
|
+
if (activeSheetId !== sheetId) {
|
|
70765
|
+
if (!this.env.model.getters.getSheet(sheetId).isVisible) {
|
|
70766
|
+
this.env.notifyUser({
|
|
70767
|
+
text: _t("The sheet on which the range is defined is hidden."),
|
|
70768
|
+
type: "info",
|
|
70769
|
+
sticky: false
|
|
70770
|
+
});
|
|
70771
|
+
return;
|
|
70772
|
+
}
|
|
70773
|
+
this.env.model.dispatch("ACTIVATE_SHEET", {
|
|
70774
|
+
sheetIdFrom: activeSheetId,
|
|
70775
|
+
sheetIdTo: sheetId
|
|
70776
|
+
});
|
|
70777
|
+
}
|
|
70778
|
+
this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
|
|
70705
70779
|
this.env.model.selection.selectZone({
|
|
70706
70780
|
cell: {
|
|
70707
70781
|
col: zone.left,
|
|
70708
70782
|
row: zone.top
|
|
70709
70783
|
},
|
|
70710
70784
|
zone
|
|
70711
|
-
});
|
|
70785
|
+
}, { allowsHiddenSelection: true });
|
|
70712
70786
|
}
|
|
70713
70787
|
get selectionKey() {
|
|
70714
70788
|
return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
|
|
@@ -87333,7 +87407,9 @@ const helpers = {
|
|
|
87333
87407
|
collapseHierarchicalDisplayName,
|
|
87334
87408
|
getCanonicalSymbolName,
|
|
87335
87409
|
fuzzyLookup,
|
|
87336
|
-
replaceSymbolInFormula
|
|
87410
|
+
replaceSymbolInFormula,
|
|
87411
|
+
isSingleCellReference,
|
|
87412
|
+
computeCachedTextDimension
|
|
87337
87413
|
};
|
|
87338
87414
|
const links = {
|
|
87339
87415
|
isMarkdownLink,
|
|
@@ -87403,7 +87479,8 @@ const components = {
|
|
|
87403
87479
|
FullScreenFigure,
|
|
87404
87480
|
NumberInput,
|
|
87405
87481
|
TopBar,
|
|
87406
|
-
Composer
|
|
87482
|
+
Composer,
|
|
87483
|
+
CalendarButton
|
|
87407
87484
|
};
|
|
87408
87485
|
const hooks = {
|
|
87409
87486
|
useDragAndDropListItems,
|
|
@@ -87463,6 +87540,6 @@ const chartHelpers = {
|
|
|
87463
87540
|
//#endregion
|
|
87464
87541
|
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
87542
|
|
|
87466
|
-
__info__.version = "19.5.0-alpha.
|
|
87467
|
-
__info__.date = "2026-
|
|
87468
|
-
__info__.hash = "
|
|
87543
|
+
__info__.version = "19.5.0-alpha.2";
|
|
87544
|
+
__info__.date = "2026-07-13T06:26:20.354Z";
|
|
87545
|
+
__info__.hash = "a4d2f90";
|