@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.
@@ -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.1
6
- * @date 2026-07-01T05:05:42.239Z
7
- * @hash 3c78107
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
- get(id) {
9537
- if (!this.colors[id]) this.colors[id] = this.availableColors.next();
9538
- return this.colors[id];
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
- const COLORSCHEMES = {
9542
- greys: [
9543
- "#ffffff",
9544
- "#808080",
9545
- "#000000"
9546
- ],
9547
- blues: [
9548
- "#f7fbff",
9549
- "#6aaed6",
9550
- "#08306b"
9551
- ],
9552
- reds: [
9553
- "#fff5f0",
9554
- "#fb694a",
9555
- "#67000d"
9556
- ],
9557
- greens: [
9558
- "#f7fcf5",
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
- * Returns a function that maps a value to a color using a color scale defined by the given
9591
- * color/threshold values pairs.
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 getColorScale(colorScalePoints) {
9594
- if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
9595
- const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
9596
- const thresholds = [];
9597
- for (let i = 1; i < sortedColorScalePoints.length; i++) {
9598
- const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
9599
- const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
9600
- const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
9601
- const maxColor = colorToNumber(sortedColorScalePoints[i].color);
9602
- thresholds.push({
9603
- min: sortedColorScalePoints[i - 1].value,
9604
- max: sortedColorScalePoints[i].value,
9605
- minColor,
9606
- maxColor,
9607
- minColorAlpha,
9608
- maxColorAlpha,
9609
- colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
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
- return (value) => {
9613
- if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
9614
- for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
9615
- return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
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 computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
9619
- const deltaValue = maxValue - minValue;
9620
- const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
9621
- const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
9622
- const deltaColorB = minColor % 256 - maxColor % 256;
9623
- return [
9624
- deltaColorR / deltaValue,
9625
- deltaColorG / deltaValue,
9626
- deltaColorB / deltaValue
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
- function colorCell(value, minValue, minColor, colorDiffUnit) {
9630
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
9631
- const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
9632
- const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
9633
- const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
9634
- return r << 16 | g << 8 | b;
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
- drawPieChartValues(chart, options, ctx);
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
- drawLineOrBarOrRadarChartValues(chart, options, ctx);
10164
+ drawRadarValues(chart, options);
10153
10165
  break;
10154
10166
  case "bar":
10155
- options.horizontal ? drawHorizontalBarChartValues(chart, options, ctx) : drawLineOrBarOrRadarChartValues(chart, options, ctx);
10167
+ options.horizontal ? drawHorizontalBarValues(chart, options) : drawVerticalBarValues(chart, options);
10156
10168
  break;
10157
10169
  case "pyramid":
10158
- drawHorizontalBarChartValues(chart, options, ctx);
10170
+ drawHorizontalBarValues(chart, options);
10159
10171
  break;
10160
10172
  case "calendar":
10161
- drawBarChartValues(chart, options, ctx);
10173
+ drawCalendarValues(chart, options);
10162
10174
  break;
10163
10175
  case "bubble":
10164
- drawBubbleChartValues(chart, options, ctx);
10176
+ drawBubbleValues(chart, options);
10165
10177
  break;
10166
10178
  case "funnel":
10167
- drawHorizontalBarChartValues(chart, options, ctx);
10179
+ drawFunnelValues(chart, options);
10168
10180
  break;
10169
10181
  }
10170
- ctx.restore();
10171
10182
  }
10172
10183
  };
10173
- function drawTextWithBackground(text, x, y, ctx) {
10174
- ctx.lineWidth = 3;
10175
- ctx.strokeText(text, x, y);
10176
- ctx.lineWidth = 1;
10177
- ctx.fillText(text, x, y);
10178
- }
10179
- function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
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 || isLineOverlayOnBarChart(options, dataset)) continue;
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 parsedValue = dataset._parsed[i];
10186
- const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
10187
- if (isNaN(value)) continue;
10188
- const point = dataset.data[i];
10189
- const xPosition = point.x;
10190
- let yPosition = 0;
10191
- if (chart.config.type === "line" || chart.config.type === "radar") yPosition = value < 0 ? point.y + 10 : point.y - 10;
10192
- else if (chart.config.type === "bubble") yPosition = point.y;
10193
- else {
10194
- const yZeroLine = yAxisScale.getPixelForValue(0);
10195
- const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10196
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10197
- if (distanceFromAxisOrigin < textHeight) yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10198
- else yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10199
- }
10200
- if (!textsPositions[xPosition]) textsPositions[xPosition] = [];
10201
- for (const otherPosition of textsPositions[xPosition] || []) if (Math.abs(otherPosition - yPosition) < MINIMAL_VERTICAL_DISTANCE) yPosition = otherPosition + MINIMAL_VERTICAL_DISTANCE * (value < 0 ? 1 : -1);
10202
- textsPositions[xPosition].push(yPosition);
10203
- ctx.fillStyle = point.options.backgroundColor;
10204
- ctx.strokeStyle = options.background(Number(value), dataset, i) || "#ffffff";
10205
- drawTextWithBackground(options.callback(Number(value), dataset, i), xPosition, yPosition, ctx);
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 drawBarChartValues(chart, options, ctx) {
10210
- const yMax = chart.chartArea.bottom;
10211
- const yMin = chart.chartArea.top;
10212
- for (const dataset of chart._metasets) {
10213
- if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
10214
- const yAxisScale = chart.scales[dataset.yAxisID];
10215
- for (let i = 0; i < dataset._parsed.length; i++) {
10216
- const parsedValue = dataset._parsed[i];
10217
- const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
10218
- if (isNaN(value)) continue;
10219
- const point = dataset.data[i];
10220
- const xPosition = point.x;
10221
- let yPosition = 0;
10222
- const yZeroLine = yAxisScale.getPixelForValue(0);
10223
- const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10224
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10225
- if (distanceFromAxisOrigin < textHeight) yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10226
- else yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10227
- yPosition = Math.min(yPosition, yMax);
10228
- yPosition = Math.max(yPosition, yMin);
10229
- ctx.strokeStyle = point.options.backgroundColor;
10230
- ctx.fillStyle = options.background(Number(value), dataset, i) || "#ffffff";
10231
- const valueToDisplay = options.callback(Number(value), dataset, i);
10232
- const measures = ctx.measureText(valueToDisplay);
10233
- if (measures.actualBoundingBoxAscent + measures.actualBoundingBoxDescent + 2 > Math.abs(point.height) - 2) continue;
10234
- drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10235
- }
10236
- }
10237
- }
10238
- function drawBubbleChartValues(chart, options, ctx) {
10239
- const yMax = chart.chartArea.bottom;
10240
- const yMin = chart.chartArea.top;
10241
- const textsPositions = {};
10242
- for (const dataset of chart._metasets) for (let i = 0; i < dataset._parsed.length; i++) {
10243
- const value = dataset._parsed[i].y;
10244
- if (isNaN(value)) continue;
10245
- const point = dataset.data[i];
10246
- const xPosition = point.x;
10247
- let yPosition = Math.max(Math.min(point.y, yMax), yMin);
10248
- if (!textsPositions[xPosition]) textsPositions[xPosition] = [];
10249
- for (const otherPosition of textsPositions[xPosition] || []) if (Math.abs(otherPosition - yPosition) < MINIMAL_VERTICAL_DISTANCE) yPosition = otherPosition + MINIMAL_VERTICAL_DISTANCE * (value < 0 ? 1 : -1);
10250
- textsPositions[xPosition].push(yPosition);
10251
- const color = point.options.backgroundColor ?? "#ffffff";
10252
- if (hexToHSLA(toHex(color)).a === 1) ctx.fillStyle = chartFontColor(color);
10253
- else ctx.fillStyle = "#000000";
10254
- const valueToDisplay = options.callback(Number(value), dataset, i);
10255
- ctx.fillText(valueToDisplay, xPosition, yPosition);
10256
- }
10257
- }
10258
- function drawHorizontalBarChartValues(chart, options, ctx) {
10259
- const textsPositions = {};
10260
- for (const dataset of chart._metasets) {
10261
- if (isTrendLineAxis(dataset.xAxisID) || isLineOverlayOnBarChart(options, dataset)) continue;
10262
- const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
10263
- for (let i = 0; i < dataset._parsed.length; i++) {
10264
- const value = Number(dataset._parsed[i].x);
10265
- if (isNaN(value)) continue;
10266
- const displayValue = options.callback(value, dataset, i);
10267
- const point = dataset.data[i];
10268
- const yPosition = point.y;
10269
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px");
10270
- const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10271
- const PADDING = 3;
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
- if (distanceFromAxisOrigin < textWidth) xPosition = value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10274
- else xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10275
- if (!textsPositions[yPosition]) textsPositions[yPosition] = [];
10276
- for (const otherPosition of textsPositions[yPosition]) if (Math.abs(otherPosition - xPosition) < textWidth) xPosition = value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10277
- textsPositions[yPosition].push(xPosition);
10278
- ctx.strokeStyle = point.options.backgroundColor;
10279
- ctx.fillStyle = options.background(Number(value), dataset, i) || "#ffffff";
10280
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10281
- }
10282
- }
10283
- }
10284
- function drawPieChartValues(chart, options, ctx) {
10285
- for (const dataset of chart._metasets) for (let i = 0; i < dataset._parsed.length; i++) {
10286
- const value = Number(dataset._parsed[i]);
10287
- if (isNaN(value) || value === 0) continue;
10288
- const bar = dataset.data[i];
10289
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10290
- const midAngle = (startAngle + endAngle) / 2;
10291
- const midRadius = (innerRadius + outerRadius) / 2;
10292
- const x = bar.x + midRadius * Math.cos(midAngle);
10293
- const y = bar.y + midRadius * Math.sin(midAngle);
10294
- const displayValue = options.callback(value, dataset, i);
10295
- const textHeight = globalThis.Chart?.defaults.font.size ?? 12;
10296
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10297
- const radius = outerRadius - innerRadius;
10298
- if (textWidth >= radius || radius < textHeight) continue;
10299
- const sliceAngle = endAngle - startAngle;
10300
- const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10301
- if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) continue;
10302
- const background = options.background(Number(value), dataset, i);
10303
- ctx.fillStyle = chartFontColor(background);
10304
- ctx.strokeStyle = background || "#ffffff";
10305
- drawTextWithBackground(displayValue, x, y, ctx);
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.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
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.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
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(getScorecardConfiguration(getZoomedRect(1 / zoom, canvas.getBoundingClientRect()), this.runtime), canvas, zoom);
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 = { CriterionInput };
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
- overlappingCarousel: void 0
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.overlappingCarousel = void 0;
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 overlappingCarousel = void 0;
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")) overlappingCarousel = this.getCarouselOverlappingChart(draggedFigure, otherFigures);
46765
- this.dnd.overlappingCarousel = overlappingCarousel;
46766
- if (!overlappingCarousel) {
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.overlappingCarousel) {
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 carouselFigureId = this.dnd.overlappingCarousel.id;
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.overlappingCarousel = void 0;
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.overlappingCarousel = void 0;
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.overlappingCarousel?.id ? "0.6" : "0.9",
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.overlappingCarousel?.id) return "";
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
- getCarouselOverlappingChart(figureUI, otherFigures) {
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 carouselCenterX = figure.x + figure.width / 2;
46933
- const carouselCenterY = figure.y + figure.height / 2;
46934
- const distanceX = Math.abs(figureCenterX - carouselCenterX);
46935
- const distanceY = Math.abs(figureCenterY - carouselCenterY);
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) continue;
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
- style[key] = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
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) this.env.model.dispatch("ACTIVATE_SHEET", {
70701
- sheetIdFrom: activeSheetId,
70702
- sheetIdTo: sheetId
70703
- });
70704
- this.env.model.selection.selectCell(zone.right, zone.bottom);
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
- clearStyleFormat(sheetId, zone) {
77009
- this.dispatch("SET_FORMATTING", {
77010
- sheetId,
77011
- target: [zone],
77012
- format: ""
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) for (const pasteZone of splitZoneForPaste(zone, content.width, content.height)) {
77020
- this.pasteStyle(sheetId, pasteZone.left, pasteZone.top, content.width, content.height, content.style);
77021
- this.pasteFormat(sheetId, pasteZone.left, pasteZone.top, content.width, content.height, content.format);
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.1";
87467
- __info__.date = "2026-07-01T05:05:42.239Z";
87468
- __info__.hash = "3c78107";
87667
+ __info__.version = "19.5.0-alpha.3";
87668
+ __info__.date = "2026-07-14T10:17:06.949Z";
87669
+ __info__.hash = "c029184";