@odoo/o-spreadsheet 19.5.0-alpha.1 → 19.5.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o_spreadsheet.cjs +778 -701
- package/dist/o_spreadsheet.css +22 -5
- package/dist/o_spreadsheet.esm.js +776 -699
- package/dist/o_spreadsheet.iife.js +778 -701
- package/dist/o_spreadsheet.min.iife.js +257 -257
- package/dist/o_spreadsheet.xml +59 -26
- package/dist/types/components/figures/chart/scorecard/chart_scorecard.d.ts +2 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +2 -2
- package/dist/types/components/side_panel/criterion_form/calendar_button/calendar_button.d.ts +13 -0
- package/dist/types/components/side_panel/criterion_form/date_criterion/date_criterion.d.ts +2 -0
- package/dist/types/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.d.ts +3 -0
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/helpers/figures/charts/scorecard_chart_config_builder.d.ts +2 -0
- package/dist/types/helpers/internal_viewport.d.ts +1 -0
- package/dist/types/helpers/text_helper.d.ts +6 -1
- package/dist/types/helpers/viewport_collection.d.ts +1 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/types/chart/scorecard_chart.d.ts +2 -0
- package/package.json +1 -1
package/dist/o_spreadsheet.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 19.5.0-alpha.
|
|
6
|
-
* @date 2026-07-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.5.0-alpha.2
|
|
6
|
+
* @date 2026-07-13T06:26:20.354Z
|
|
7
|
+
* @hash a4d2f90
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
@@ -563,6 +563,7 @@ const DEFAULT_NUMBER_STYLE = {
|
|
|
563
563
|
};
|
|
564
564
|
const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
|
|
565
565
|
const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
|
|
566
|
+
const DEFAULT_TEXT_HIGHLIGHT_PERCENT = .25;
|
|
566
567
|
const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
|
|
567
568
|
const DEFAULT_FONT = "'Roboto', arial, 'Liberation Sans'";
|
|
568
569
|
const DEFAULT_BORDER_DESC = {
|
|
@@ -2600,7 +2601,7 @@ function unregisterChartJsExtensions() {
|
|
|
2600
2601
|
*
|
|
2601
2602
|
* The end goal is to eliminate all compatibility shims.
|
|
2602
2603
|
*/
|
|
2603
|
-
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$24, onPatched: onPatched$5, onWillUnmount: onWillUnmount$18, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props: props$
|
|
2604
|
+
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$24, onPatched: onPatched$5, onWillUnmount: onWillUnmount$18, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props: props$167, providePlugins, useScope, xml: xml$4 } = _odoo_owl;
|
|
2604
2605
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2605
2606
|
var _Component = class extends OwlComponent {
|
|
2606
2607
|
static template = "";
|
|
@@ -2730,7 +2731,7 @@ var Portal = class extends OwlComponent {
|
|
|
2730
2731
|
static template = xml$4`<t t-call-slot="default"/>`;
|
|
2731
2732
|
constructor(node) {
|
|
2732
2733
|
super(node);
|
|
2733
|
-
this.props = props$
|
|
2734
|
+
this.props = props$167();
|
|
2734
2735
|
this.__owl__ = node;
|
|
2735
2736
|
}
|
|
2736
2737
|
setup() {
|
|
@@ -8406,534 +8407,6 @@ function addOrigin(cell, origin) {
|
|
|
8406
8407
|
return cell;
|
|
8407
8408
|
}
|
|
8408
8409
|
|
|
8409
|
-
//#endregion
|
|
8410
|
-
//#region src/helpers/text_helper.ts
|
|
8411
|
-
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
8412
|
-
return numberOfLines * (textLineHeight + 4) - 4;
|
|
8413
|
-
}
|
|
8414
|
-
function getCanvas(width = 100, height = 100) {
|
|
8415
|
-
return new OffscreenCanvas(width, height).getContext("2d");
|
|
8416
|
-
}
|
|
8417
|
-
/**
|
|
8418
|
-
* Get the default height of the cell given its style.
|
|
8419
|
-
*/
|
|
8420
|
-
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
8421
|
-
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
8422
|
-
let content = "";
|
|
8423
|
-
try {
|
|
8424
|
-
if (!cell.isFormula) {
|
|
8425
|
-
const localeFormat = {
|
|
8426
|
-
format: cell.format,
|
|
8427
|
-
locale
|
|
8428
|
-
};
|
|
8429
|
-
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
8430
|
-
}
|
|
8431
|
-
} catch {
|
|
8432
|
-
content = CellErrorType.GenericError;
|
|
8433
|
-
}
|
|
8434
|
-
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
8435
|
-
}
|
|
8436
|
-
function getCellContentHeight(ctx, content, style, colSize) {
|
|
8437
|
-
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
8438
|
-
}
|
|
8439
|
-
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
8440
|
-
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
8441
|
-
}
|
|
8442
|
-
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
8443
|
-
if (!textLines.length) return {
|
|
8444
|
-
width: 0,
|
|
8445
|
-
height: 0
|
|
8446
|
-
};
|
|
8447
|
-
const font = computeTextFont(style, fontUnit);
|
|
8448
|
-
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
8449
|
-
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
8450
|
-
const width = Math.max(...sizes.map((size) => size.width));
|
|
8451
|
-
if (!style.rotation) return {
|
|
8452
|
-
height,
|
|
8453
|
-
width
|
|
8454
|
-
};
|
|
8455
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8456
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8457
|
-
return {
|
|
8458
|
-
width: width * cos + height * sin,
|
|
8459
|
-
height: sin * width + cos * height
|
|
8460
|
-
};
|
|
8461
|
-
}
|
|
8462
|
-
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
8463
|
-
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
8464
|
-
}
|
|
8465
|
-
function computeCachedTextWidth(context, text, font, rotation) {
|
|
8466
|
-
const size = computeCachedTextDimension(context, text, font);
|
|
8467
|
-
if (!rotation) return size.width;
|
|
8468
|
-
const cos = Math.abs(Math.cos(rotation));
|
|
8469
|
-
const sin = Math.abs(Math.sin(rotation));
|
|
8470
|
-
return size.width * cos + size.height * sin;
|
|
8471
|
-
}
|
|
8472
|
-
const textDimensionsCache = {};
|
|
8473
|
-
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
8474
|
-
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
8475
|
-
if (!style.rotation) return size;
|
|
8476
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8477
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8478
|
-
return {
|
|
8479
|
-
width: size.width * cos + size.height * sin,
|
|
8480
|
-
height: size.height * cos + size.width * sin
|
|
8481
|
-
};
|
|
8482
|
-
}
|
|
8483
|
-
function computeCachedTextDimension(context, text, font) {
|
|
8484
|
-
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
8485
|
-
if (textDimensionsCache[font][text] === void 0) {
|
|
8486
|
-
context.save();
|
|
8487
|
-
context.font = font;
|
|
8488
|
-
const measure = context.measureText(text);
|
|
8489
|
-
context.restore();
|
|
8490
|
-
const width = measure.width;
|
|
8491
|
-
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8492
|
-
textDimensionsCache[font][text] = {
|
|
8493
|
-
width,
|
|
8494
|
-
height
|
|
8495
|
-
};
|
|
8496
|
-
}
|
|
8497
|
-
return textDimensionsCache[font][text];
|
|
8498
|
-
}
|
|
8499
|
-
function fontSizeInPixels(fontSize) {
|
|
8500
|
-
return Math.round(fontSize * 96 / 72);
|
|
8501
|
-
}
|
|
8502
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
8503
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
8504
|
-
}
|
|
8505
|
-
function computeTextFontSizeInPixels(style) {
|
|
8506
|
-
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
8507
|
-
}
|
|
8508
|
-
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
8509
|
-
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
8510
|
-
const splitWord = [];
|
|
8511
|
-
let wordPart = "";
|
|
8512
|
-
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
8513
|
-
splitWord.push(wordPart);
|
|
8514
|
-
wordPart = l;
|
|
8515
|
-
} else wordPart += l;
|
|
8516
|
-
splitWord.push(wordPart);
|
|
8517
|
-
return splitWord;
|
|
8518
|
-
}
|
|
8519
|
-
/**
|
|
8520
|
-
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
8521
|
-
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
8522
|
-
*/
|
|
8523
|
-
function splitTextToWidth(ctx, text, style, width) {
|
|
8524
|
-
if (!style) style = {};
|
|
8525
|
-
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
8526
|
-
const brokenText = [];
|
|
8527
|
-
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
8528
|
-
for (const line of lines) {
|
|
8529
|
-
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
8530
|
-
if (!width) {
|
|
8531
|
-
brokenText.push(line);
|
|
8532
|
-
continue;
|
|
8533
|
-
}
|
|
8534
|
-
let textLine = "";
|
|
8535
|
-
let availableWidth = width;
|
|
8536
|
-
for (const word of words) {
|
|
8537
|
-
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
8538
|
-
const lastPart = splitWord.pop();
|
|
8539
|
-
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
8540
|
-
if (splitWord.length) {
|
|
8541
|
-
if (textLine !== "") {
|
|
8542
|
-
brokenText.push(textLine);
|
|
8543
|
-
textLine = "";
|
|
8544
|
-
availableWidth = width;
|
|
8545
|
-
}
|
|
8546
|
-
splitWord.forEach((wordPart) => {
|
|
8547
|
-
brokenText.push(wordPart);
|
|
8548
|
-
});
|
|
8549
|
-
textLine = lastPart;
|
|
8550
|
-
availableWidth = width - lastPartWidth;
|
|
8551
|
-
} else {
|
|
8552
|
-
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
8553
|
-
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
8554
|
-
if (wordWidth <= availableWidth) {
|
|
8555
|
-
textLine += _word;
|
|
8556
|
-
availableWidth -= wordWidth;
|
|
8557
|
-
} else {
|
|
8558
|
-
brokenText.push(textLine);
|
|
8559
|
-
textLine = lastPart;
|
|
8560
|
-
availableWidth = width - lastPartWidth;
|
|
8561
|
-
}
|
|
8562
|
-
}
|
|
8563
|
-
}
|
|
8564
|
-
if (textLine !== "") brokenText.push(textLine);
|
|
8565
|
-
}
|
|
8566
|
-
return brokenText;
|
|
8567
|
-
}
|
|
8568
|
-
/**
|
|
8569
|
-
* Return the font size that makes the width of a text match the given line width.
|
|
8570
|
-
* Minimum font size is 1.
|
|
8571
|
-
*
|
|
8572
|
-
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
8573
|
-
*/
|
|
8574
|
-
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
8575
|
-
let minFontSize = 1;
|
|
8576
|
-
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
8577
|
-
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
8578
|
-
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
8579
|
-
let currentTextWidth = getTextWidth(fontSize);
|
|
8580
|
-
let iterations = 0;
|
|
8581
|
-
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
8582
|
-
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
8583
|
-
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
8584
|
-
fontSize = (minFontSize + maxFontSize) / 2;
|
|
8585
|
-
currentTextWidth = getTextWidth(fontSize);
|
|
8586
|
-
iterations++;
|
|
8587
|
-
}
|
|
8588
|
-
return fontSize;
|
|
8589
|
-
}
|
|
8590
|
-
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
8591
|
-
function toTrimmedLowerCase(str) {
|
|
8592
|
-
return str ? str.toLowerCase().trim() : "";
|
|
8593
|
-
}
|
|
8594
|
-
/**
|
|
8595
|
-
* Extract the fontSize from a context font string
|
|
8596
|
-
* @param font The (context) font string to parse
|
|
8597
|
-
* @returns The fontSize in pixels
|
|
8598
|
-
*/
|
|
8599
|
-
const pxRegex = /([0-9\.]*)px/;
|
|
8600
|
-
function getContextFontSize(font) {
|
|
8601
|
-
return Number(font.match(pxRegex)?.[1]);
|
|
8602
|
-
}
|
|
8603
|
-
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
8604
|
-
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8605
|
-
if (width <= maxWidth) return text;
|
|
8606
|
-
const ellipsis = "…";
|
|
8607
|
-
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
8608
|
-
if (width <= ellipsisWidth) return text;
|
|
8609
|
-
let len = text.length;
|
|
8610
|
-
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
8611
|
-
text = text.substring(0, len);
|
|
8612
|
-
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8613
|
-
}
|
|
8614
|
-
return text + ellipsis;
|
|
8615
|
-
}
|
|
8616
|
-
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10) {
|
|
8617
|
-
context.fillText(text, position.x, position.y);
|
|
8618
|
-
if (!underline && !strikethrough) return;
|
|
8619
|
-
const measure = context.measureText(text);
|
|
8620
|
-
const textWidth = measure.width;
|
|
8621
|
-
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
8622
|
-
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8623
|
-
let { x, y } = position;
|
|
8624
|
-
let strikeY = y, underlineY = y;
|
|
8625
|
-
switch (context.textAlign) {
|
|
8626
|
-
case "center":
|
|
8627
|
-
x -= textWidth / 2;
|
|
8628
|
-
break;
|
|
8629
|
-
case "right":
|
|
8630
|
-
x -= textWidth;
|
|
8631
|
-
break;
|
|
8632
|
-
}
|
|
8633
|
-
switch (context.textBaseline) {
|
|
8634
|
-
case "top":
|
|
8635
|
-
underlineY += boxHeight - 2 * strokeWidth;
|
|
8636
|
-
strikeY += boxHeight / 2 - strokeWidth;
|
|
8637
|
-
break;
|
|
8638
|
-
case "middle":
|
|
8639
|
-
underlineY += boxHeight / 2 - strokeWidth;
|
|
8640
|
-
break;
|
|
8641
|
-
case "alphabetic":
|
|
8642
|
-
underlineY += 2 * strokeWidth;
|
|
8643
|
-
strikeY -= 3 * strokeWidth;
|
|
8644
|
-
break;
|
|
8645
|
-
case "bottom":
|
|
8646
|
-
underlineY = y;
|
|
8647
|
-
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
8648
|
-
break;
|
|
8649
|
-
}
|
|
8650
|
-
if (underline) {
|
|
8651
|
-
context.lineWidth = strokeWidth;
|
|
8652
|
-
context.strokeStyle = context.fillStyle;
|
|
8653
|
-
context.beginPath();
|
|
8654
|
-
context.moveTo(x, underlineY);
|
|
8655
|
-
context.lineTo(x + textWidth, underlineY);
|
|
8656
|
-
context.stroke();
|
|
8657
|
-
}
|
|
8658
|
-
if (strikethrough) {
|
|
8659
|
-
context.lineWidth = strokeWidth;
|
|
8660
|
-
context.strokeStyle = context.fillStyle;
|
|
8661
|
-
context.beginPath();
|
|
8662
|
-
context.moveTo(x, strikeY);
|
|
8663
|
-
context.lineTo(x + textWidth, strikeY);
|
|
8664
|
-
context.stroke();
|
|
8665
|
-
}
|
|
8666
|
-
}
|
|
8667
|
-
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
8668
|
-
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
8669
|
-
const ellipsis = "...";
|
|
8670
|
-
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
8671
|
-
if (ellipsisWidth >= width) return "";
|
|
8672
|
-
let lowerBoundLen = 1;
|
|
8673
|
-
let upperBoundLen = text.length;
|
|
8674
|
-
let currentWidth;
|
|
8675
|
-
while (lowerBoundLen <= upperBoundLen) {
|
|
8676
|
-
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
8677
|
-
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
8678
|
-
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
8679
|
-
else lowerBoundLen = currentLen + 1;
|
|
8680
|
-
}
|
|
8681
|
-
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
8682
|
-
return slicedText ? slicedText + ellipsis : "";
|
|
8683
|
-
}
|
|
8684
|
-
/**
|
|
8685
|
-
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
8686
|
-
* with to original's text vertical and horizontal alignment.
|
|
8687
|
-
*/
|
|
8688
|
-
function computeRotationPosition(rect, style) {
|
|
8689
|
-
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
8690
|
-
let { x, y } = rect;
|
|
8691
|
-
const cos = Math.cos(-style.rotation);
|
|
8692
|
-
const sin = Math.sin(-style.rotation);
|
|
8693
|
-
const width = rect.textWidth - 2 * 4;
|
|
8694
|
-
const height = rect.textHeight;
|
|
8695
|
-
const center = style.align === "center";
|
|
8696
|
-
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
8697
|
-
const sh = sin * height;
|
|
8698
|
-
const sw = Math.abs(sin * width);
|
|
8699
|
-
const ch = cos * height;
|
|
8700
|
-
if (style.verticalAlign === "top") if (center) {
|
|
8701
|
-
y += sw / 2;
|
|
8702
|
-
x -= sh / 2;
|
|
8703
|
-
} else if (rotateTowardCellCenter) x -= sh;
|
|
8704
|
-
else y += sw;
|
|
8705
|
-
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
8706
|
-
y += height - ch;
|
|
8707
|
-
if (center) {
|
|
8708
|
-
y -= sw / 2;
|
|
8709
|
-
x -= sh / 2;
|
|
8710
|
-
} else if (rotateTowardCellCenter) {
|
|
8711
|
-
x -= sh;
|
|
8712
|
-
y -= sw;
|
|
8713
|
-
}
|
|
8714
|
-
} else if (center) {
|
|
8715
|
-
x -= sh / 2;
|
|
8716
|
-
y -= height / 2;
|
|
8717
|
-
if (rotateTowardCellCenter) y += sh;
|
|
8718
|
-
else y -= sh;
|
|
8719
|
-
} else if (rotateTowardCellCenter) {
|
|
8720
|
-
x -= sh;
|
|
8721
|
-
y -= sw / 2;
|
|
8722
|
-
} else y += sw / 2 + ch / 4;
|
|
8723
|
-
return {
|
|
8724
|
-
x: cos * x - sin * y,
|
|
8725
|
-
y: cos * y + sin * x
|
|
8726
|
-
};
|
|
8727
|
-
}
|
|
8728
|
-
|
|
8729
|
-
//#endregion
|
|
8730
|
-
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
8731
|
-
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
8732
|
-
const chartColorScalePlugin = {
|
|
8733
|
-
id: "chartColorScalePlugin",
|
|
8734
|
-
afterDatasetsDraw(chart, args, options) {
|
|
8735
|
-
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
8736
|
-
const ctx = chart.ctx;
|
|
8737
|
-
ctx.save();
|
|
8738
|
-
ctx.textAlign = "center";
|
|
8739
|
-
ctx.textBaseline = "middle";
|
|
8740
|
-
ctx.miterLimit = 1;
|
|
8741
|
-
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
8742
|
-
const gradientWidth = 10;
|
|
8743
|
-
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
8744
|
-
const gradientY = chart.chartArea.top;
|
|
8745
|
-
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
8746
|
-
const step = 1 / (options.colorScale.length - 1);
|
|
8747
|
-
options.colorScale.forEach((color, index) => {
|
|
8748
|
-
gradient.addColorStop(index * step, color);
|
|
8749
|
-
});
|
|
8750
|
-
ctx.fillStyle = gradient;
|
|
8751
|
-
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
8752
|
-
ctx.fillStyle = options.fontColor ?? "black";
|
|
8753
|
-
ctx.font = getDefaultContextFont(12);
|
|
8754
|
-
ctx.textAlign = "left";
|
|
8755
|
-
let minValue = Math.round(options.minValue * 100) / 100;
|
|
8756
|
-
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
8757
|
-
if (options.minValue === options.maxValue) {
|
|
8758
|
-
minValue -= 1;
|
|
8759
|
-
maxValue += 1;
|
|
8760
|
-
}
|
|
8761
|
-
const formattedMaxValue = humanizeNumber({
|
|
8762
|
-
value: maxValue,
|
|
8763
|
-
format: void 0
|
|
8764
|
-
}, options.locale);
|
|
8765
|
-
const formattedMinValue = humanizeNumber({
|
|
8766
|
-
value: minValue,
|
|
8767
|
-
format: void 0
|
|
8768
|
-
}, options.locale);
|
|
8769
|
-
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
8770
|
-
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
8771
|
-
ctx.restore();
|
|
8772
|
-
}
|
|
8773
|
-
};
|
|
8774
|
-
|
|
8775
|
-
//#endregion
|
|
8776
|
-
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
8777
|
-
function getFunnelChartController() {
|
|
8778
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8779
|
-
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
8780
|
-
static id = "funnel";
|
|
8781
|
-
static defaults = {
|
|
8782
|
-
...globalThis.Chart?.BarController.defaults,
|
|
8783
|
-
dataElementType: "funnel",
|
|
8784
|
-
animation: { duration: (ctx) => {
|
|
8785
|
-
if (ctx.type !== "data") return 1e3;
|
|
8786
|
-
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
8787
|
-
} }
|
|
8788
|
-
};
|
|
8789
|
-
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
8790
|
-
updateElements(rects, start, count, mode) {
|
|
8791
|
-
super.updateElements(rects, start, count, mode);
|
|
8792
|
-
for (let i = start; i < start + count; i++) {
|
|
8793
|
-
const rect = rects[i];
|
|
8794
|
-
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
8795
|
-
}
|
|
8796
|
-
}
|
|
8797
|
-
};
|
|
8798
|
-
}
|
|
8799
|
-
function getFunnelChartElement() {
|
|
8800
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8801
|
-
/**
|
|
8802
|
-
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
8803
|
-
* `width`, and the bottom is of width `nextElementWidth`.
|
|
8804
|
-
*/
|
|
8805
|
-
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
8806
|
-
static id = "funnel";
|
|
8807
|
-
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
8808
|
-
draw(ctx) {
|
|
8809
|
-
ctx.save();
|
|
8810
|
-
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
8811
|
-
"x",
|
|
8812
|
-
"y",
|
|
8813
|
-
"width",
|
|
8814
|
-
"height",
|
|
8815
|
-
"nextElement",
|
|
8816
|
-
"base",
|
|
8817
|
-
"options"
|
|
8818
|
-
]);
|
|
8819
|
-
const width = getElementWidth(this);
|
|
8820
|
-
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
8821
|
-
const startX = Math.min(x, base);
|
|
8822
|
-
const startY = y - height / 2;
|
|
8823
|
-
ctx.fillStyle = options.backgroundColor;
|
|
8824
|
-
ctx.beginPath();
|
|
8825
|
-
ctx.moveTo(startX, startY);
|
|
8826
|
-
ctx.lineTo(startX + width, startY);
|
|
8827
|
-
ctx.lineTo(startX + width - offset, startY + height);
|
|
8828
|
-
ctx.lineTo(startX + offset, startY + height);
|
|
8829
|
-
ctx.closePath();
|
|
8830
|
-
ctx.fill();
|
|
8831
|
-
if (options.borderWidth) {
|
|
8832
|
-
ctx.strokeStyle = options.borderColor;
|
|
8833
|
-
ctx.lineWidth = options.borderWidth;
|
|
8834
|
-
ctx.stroke();
|
|
8835
|
-
}
|
|
8836
|
-
ctx.restore();
|
|
8837
|
-
}
|
|
8838
|
-
/** Check if the mouse is inside the trapezoid */
|
|
8839
|
-
inRange(mouseX, mouseY) {
|
|
8840
|
-
const { x, y, height, nextElement, base } = this.getProps([
|
|
8841
|
-
"x",
|
|
8842
|
-
"y",
|
|
8843
|
-
"width",
|
|
8844
|
-
"height",
|
|
8845
|
-
"nextElement",
|
|
8846
|
-
"base",
|
|
8847
|
-
"options"
|
|
8848
|
-
]);
|
|
8849
|
-
const width = getElementWidth(this);
|
|
8850
|
-
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
8851
|
-
const startX = Math.min(x, base);
|
|
8852
|
-
const startY = y - height / 2;
|
|
8853
|
-
if (mouseY < startY || mouseY > startY + height) return false;
|
|
8854
|
-
const offset = (width - nextElementWidth) / 2;
|
|
8855
|
-
const left = startX + offset * (mouseY - startY) / height;
|
|
8856
|
-
const right = startX + width - offset * (mouseY - startY) / height;
|
|
8857
|
-
if (mouseX < left || mouseX > right) return false;
|
|
8858
|
-
return true;
|
|
8859
|
-
}
|
|
8860
|
-
};
|
|
8861
|
-
}
|
|
8862
|
-
/**
|
|
8863
|
-
* Get an element width.
|
|
8864
|
-
*
|
|
8865
|
-
* The property width is undefined during animations, we need to compute it manually.
|
|
8866
|
-
*/
|
|
8867
|
-
function getElementWidth(element) {
|
|
8868
|
-
const { x, base } = element.getProps(["x", "base"]);
|
|
8869
|
-
return Math.max(x, base) - Math.min(x, base);
|
|
8870
|
-
}
|
|
8871
|
-
/**
|
|
8872
|
-
* Position the tooltip inside the trapezoid.
|
|
8873
|
-
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
8874
|
-
*/
|
|
8875
|
-
const funnelTooltipPositioner = function(elements) {
|
|
8876
|
-
if (!elements.length) return {
|
|
8877
|
-
x: 0,
|
|
8878
|
-
y: 0
|
|
8879
|
-
};
|
|
8880
|
-
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
8881
|
-
"x",
|
|
8882
|
-
"y",
|
|
8883
|
-
"width",
|
|
8884
|
-
"height",
|
|
8885
|
-
"base"
|
|
8886
|
-
]);
|
|
8887
|
-
const startX = Math.min(x, base);
|
|
8888
|
-
const startY = y - height / 2;
|
|
8889
|
-
return {
|
|
8890
|
-
x: startX + width * 2 / 3,
|
|
8891
|
-
y: startY + height / 2
|
|
8892
|
-
};
|
|
8893
|
-
};
|
|
8894
|
-
|
|
8895
|
-
//#endregion
|
|
8896
|
-
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
8897
|
-
const chartMinorGridPlugin = {
|
|
8898
|
-
id: "o-spreadsheet-minor-gridlines",
|
|
8899
|
-
beforeDatasetsDraw(chart) {
|
|
8900
|
-
const ctx = chart.ctx;
|
|
8901
|
-
const chartArea = chart.chartArea;
|
|
8902
|
-
if (!chartArea) return;
|
|
8903
|
-
for (const scaleId in chart.scales) {
|
|
8904
|
-
const scale = chart.scales[scaleId];
|
|
8905
|
-
const options = scale.options;
|
|
8906
|
-
const minor = options?.grid?.minor;
|
|
8907
|
-
if (!minor?.display) continue;
|
|
8908
|
-
const showMajorGrid = options?.grid?.display;
|
|
8909
|
-
const ticks = scale.ticks;
|
|
8910
|
-
if (!ticks || ticks.length < 2) continue;
|
|
8911
|
-
ctx.save();
|
|
8912
|
-
ctx.lineWidth = 1;
|
|
8913
|
-
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
8914
|
-
for (let i = 0; i < ticks.length - 1; i++) {
|
|
8915
|
-
const start = scale.getPixelForTick(i);
|
|
8916
|
-
const end = scale.getPixelForTick(i + 1);
|
|
8917
|
-
if (!isFinite(start) || !isFinite(end)) continue;
|
|
8918
|
-
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
8919
|
-
const ratio = j / 4;
|
|
8920
|
-
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
8921
|
-
ctx.beginPath();
|
|
8922
|
-
if (scale.isHorizontal()) {
|
|
8923
|
-
ctx.moveTo(position, chartArea.top);
|
|
8924
|
-
ctx.lineTo(position, chartArea.bottom);
|
|
8925
|
-
} else {
|
|
8926
|
-
ctx.moveTo(chartArea.left, position);
|
|
8927
|
-
ctx.lineTo(chartArea.right, position);
|
|
8928
|
-
}
|
|
8929
|
-
ctx.stroke();
|
|
8930
|
-
}
|
|
8931
|
-
}
|
|
8932
|
-
ctx.restore();
|
|
8933
|
-
}
|
|
8934
|
-
}
|
|
8935
|
-
};
|
|
8936
|
-
|
|
8937
8410
|
//#endregion
|
|
8938
8411
|
//#region src/helpers/color.ts
|
|
8939
8412
|
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
|
|
@@ -9498,142 +8971,675 @@ const ALTERNATING_COLORS_XL = [
|
|
|
9498
8971
|
function getNthColor(index, palette) {
|
|
9499
8972
|
return palette[index % palette.length];
|
|
9500
8973
|
}
|
|
9501
|
-
function getColorsPalette(quantity) {
|
|
9502
|
-
if (quantity <= 6) return COLORS_SM;
|
|
9503
|
-
else if (quantity <= 12) return COLORS_MD;
|
|
9504
|
-
else if (quantity <= 24) return COLORS_LG;
|
|
9505
|
-
else return COLORS_XL;
|
|
8974
|
+
function getColorsPalette(quantity) {
|
|
8975
|
+
if (quantity <= 6) return COLORS_SM;
|
|
8976
|
+
else if (quantity <= 12) return COLORS_MD;
|
|
8977
|
+
else if (quantity <= 24) return COLORS_LG;
|
|
8978
|
+
else return COLORS_XL;
|
|
8979
|
+
}
|
|
8980
|
+
function getAlternatingColorsPalette(quantity) {
|
|
8981
|
+
if (quantity <= 6) return COLORS_SM;
|
|
8982
|
+
else if (quantity <= 12) return ALTERNATING_COLORS_MD;
|
|
8983
|
+
else if (quantity <= 24) return ALTERNATING_COLORS_LG;
|
|
8984
|
+
else return ALTERNATING_COLORS_XL;
|
|
8985
|
+
}
|
|
8986
|
+
var ColorGenerator = class {
|
|
8987
|
+
preferredColors;
|
|
8988
|
+
currentColorIndex = 0;
|
|
8989
|
+
palette;
|
|
8990
|
+
constructor(paletteSize, preferredColors = []) {
|
|
8991
|
+
this.preferredColors = preferredColors;
|
|
8992
|
+
this.palette = getColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
8993
|
+
}
|
|
8994
|
+
next() {
|
|
8995
|
+
return this.preferredColors?.[this.currentColorIndex] ? this.preferredColors[this.currentColorIndex++] : getNthColor(this.currentColorIndex++, this.palette);
|
|
8996
|
+
}
|
|
8997
|
+
};
|
|
8998
|
+
var AlternatingColorGenerator = class extends ColorGenerator {
|
|
8999
|
+
constructor(paletteSize, preferredColors = []) {
|
|
9000
|
+
super(paletteSize, preferredColors);
|
|
9001
|
+
this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
9002
|
+
}
|
|
9003
|
+
};
|
|
9004
|
+
var AlternatingColorMap = class {
|
|
9005
|
+
availableColors;
|
|
9006
|
+
colors = {};
|
|
9007
|
+
constructor(paletteSize = 12) {
|
|
9008
|
+
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9009
|
+
}
|
|
9010
|
+
get(id) {
|
|
9011
|
+
if (!this.colors[id]) this.colors[id] = this.availableColors.next();
|
|
9012
|
+
return this.colors[id];
|
|
9013
|
+
}
|
|
9014
|
+
};
|
|
9015
|
+
const COLORSCHEMES = {
|
|
9016
|
+
greys: [
|
|
9017
|
+
"#ffffff",
|
|
9018
|
+
"#808080",
|
|
9019
|
+
"#000000"
|
|
9020
|
+
],
|
|
9021
|
+
blues: [
|
|
9022
|
+
"#f7fbff",
|
|
9023
|
+
"#6aaed6",
|
|
9024
|
+
"#08306b"
|
|
9025
|
+
],
|
|
9026
|
+
reds: [
|
|
9027
|
+
"#fff5f0",
|
|
9028
|
+
"#fb694a",
|
|
9029
|
+
"#67000d"
|
|
9030
|
+
],
|
|
9031
|
+
greens: [
|
|
9032
|
+
"#f7fcf5",
|
|
9033
|
+
"#73c476",
|
|
9034
|
+
"#00441b"
|
|
9035
|
+
],
|
|
9036
|
+
oranges: [
|
|
9037
|
+
"#fff5eb",
|
|
9038
|
+
"#fd8c3b",
|
|
9039
|
+
"#7f2704"
|
|
9040
|
+
],
|
|
9041
|
+
purples: [
|
|
9042
|
+
"#fcfbfd",
|
|
9043
|
+
"#9e9ac8",
|
|
9044
|
+
"#3f007d"
|
|
9045
|
+
],
|
|
9046
|
+
viridis: [
|
|
9047
|
+
"#440154",
|
|
9048
|
+
"#21918c",
|
|
9049
|
+
"#fde725"
|
|
9050
|
+
],
|
|
9051
|
+
cividis: [
|
|
9052
|
+
"#00224e",
|
|
9053
|
+
"#7d7c78",
|
|
9054
|
+
"#fee838"
|
|
9055
|
+
],
|
|
9056
|
+
rainbow: [
|
|
9057
|
+
"#B41DB4",
|
|
9058
|
+
"#FFFF00",
|
|
9059
|
+
"#00FFFF"
|
|
9060
|
+
]
|
|
9061
|
+
};
|
|
9062
|
+
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9063
|
+
/**
|
|
9064
|
+
* Returns a function that maps a value to a color using a color scale defined by the given
|
|
9065
|
+
* color/threshold values pairs.
|
|
9066
|
+
*/
|
|
9067
|
+
function getColorScale(colorScalePoints) {
|
|
9068
|
+
if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
|
|
9069
|
+
const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
|
|
9070
|
+
const thresholds = [];
|
|
9071
|
+
for (let i = 1; i < sortedColorScalePoints.length; i++) {
|
|
9072
|
+
const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
|
|
9073
|
+
const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
|
|
9074
|
+
const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
|
|
9075
|
+
const maxColor = colorToNumber(sortedColorScalePoints[i].color);
|
|
9076
|
+
thresholds.push({
|
|
9077
|
+
min: sortedColorScalePoints[i - 1].value,
|
|
9078
|
+
max: sortedColorScalePoints[i].value,
|
|
9079
|
+
minColor,
|
|
9080
|
+
maxColor,
|
|
9081
|
+
minColorAlpha,
|
|
9082
|
+
maxColorAlpha,
|
|
9083
|
+
colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
|
|
9084
|
+
});
|
|
9085
|
+
}
|
|
9086
|
+
return (value) => {
|
|
9087
|
+
if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
|
|
9088
|
+
for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
|
|
9089
|
+
return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
|
|
9090
|
+
};
|
|
9091
|
+
}
|
|
9092
|
+
function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
|
|
9093
|
+
const deltaValue = maxValue - minValue;
|
|
9094
|
+
const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
|
|
9095
|
+
const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
|
|
9096
|
+
const deltaColorB = minColor % 256 - maxColor % 256;
|
|
9097
|
+
return [
|
|
9098
|
+
deltaColorR / deltaValue,
|
|
9099
|
+
deltaColorG / deltaValue,
|
|
9100
|
+
deltaColorB / deltaValue
|
|
9101
|
+
];
|
|
9102
|
+
}
|
|
9103
|
+
function colorCell(value, minValue, minColor, colorDiffUnit) {
|
|
9104
|
+
const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
|
|
9105
|
+
const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
|
|
9106
|
+
const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
|
|
9107
|
+
const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
|
|
9108
|
+
return r << 16 | g << 8 | b;
|
|
9109
|
+
}
|
|
9110
|
+
|
|
9111
|
+
//#endregion
|
|
9112
|
+
//#region src/helpers/text_helper.ts
|
|
9113
|
+
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
9114
|
+
return numberOfLines * (textLineHeight + 4) - 4;
|
|
9115
|
+
}
|
|
9116
|
+
function getCanvas(width = 100, height = 100) {
|
|
9117
|
+
return new OffscreenCanvas(width, height).getContext("2d");
|
|
9118
|
+
}
|
|
9119
|
+
/**
|
|
9120
|
+
* Get the default height of the cell given its style.
|
|
9121
|
+
*/
|
|
9122
|
+
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
9123
|
+
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
9124
|
+
let content = "";
|
|
9125
|
+
try {
|
|
9126
|
+
if (!cell.isFormula) {
|
|
9127
|
+
const localeFormat = {
|
|
9128
|
+
format: cell.format,
|
|
9129
|
+
locale
|
|
9130
|
+
};
|
|
9131
|
+
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
9132
|
+
}
|
|
9133
|
+
} catch {
|
|
9134
|
+
content = CellErrorType.GenericError;
|
|
9135
|
+
}
|
|
9136
|
+
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
9137
|
+
}
|
|
9138
|
+
function getCellContentHeight(ctx, content, style, colSize) {
|
|
9139
|
+
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
9140
|
+
}
|
|
9141
|
+
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
9142
|
+
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
9143
|
+
}
|
|
9144
|
+
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
9145
|
+
if (!textLines.length) return {
|
|
9146
|
+
width: 0,
|
|
9147
|
+
height: 0
|
|
9148
|
+
};
|
|
9149
|
+
const font = computeTextFont(style, fontUnit);
|
|
9150
|
+
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
9151
|
+
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
9152
|
+
const width = Math.max(...sizes.map((size) => size.width));
|
|
9153
|
+
if (!style.rotation) return {
|
|
9154
|
+
height,
|
|
9155
|
+
width
|
|
9156
|
+
};
|
|
9157
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9158
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9159
|
+
return {
|
|
9160
|
+
width: width * cos + height * sin,
|
|
9161
|
+
height: sin * width + cos * height
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
9165
|
+
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
9166
|
+
}
|
|
9167
|
+
function computeCachedTextWidth(context, text, font, rotation) {
|
|
9168
|
+
const size = computeCachedTextDimension(context, text, font);
|
|
9169
|
+
if (!rotation) return size.width;
|
|
9170
|
+
const cos = Math.abs(Math.cos(rotation));
|
|
9171
|
+
const sin = Math.abs(Math.sin(rotation));
|
|
9172
|
+
return size.width * cos + size.height * sin;
|
|
9173
|
+
}
|
|
9174
|
+
const textDimensionsCache = {};
|
|
9175
|
+
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
9176
|
+
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
9177
|
+
if (!style.rotation) return size;
|
|
9178
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9179
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9180
|
+
return {
|
|
9181
|
+
width: size.width * cos + size.height * sin,
|
|
9182
|
+
height: size.height * cos + size.width * sin
|
|
9183
|
+
};
|
|
9184
|
+
}
|
|
9185
|
+
function computeCachedTextDimension(context, text, font) {
|
|
9186
|
+
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
9187
|
+
if (textDimensionsCache[font][text] === void 0) {
|
|
9188
|
+
context.save();
|
|
9189
|
+
context.font = font;
|
|
9190
|
+
const measure = context.measureText(text);
|
|
9191
|
+
context.restore();
|
|
9192
|
+
const width = measure.width;
|
|
9193
|
+
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9194
|
+
textDimensionsCache[font][text] = {
|
|
9195
|
+
width,
|
|
9196
|
+
height
|
|
9197
|
+
};
|
|
9198
|
+
}
|
|
9199
|
+
return textDimensionsCache[font][text];
|
|
9200
|
+
}
|
|
9201
|
+
function fontSizeInPixels(fontSize) {
|
|
9202
|
+
return Math.round(fontSize * 96 / 72);
|
|
9203
|
+
}
|
|
9204
|
+
function computeTextFont(style, fontUnit = "pt") {
|
|
9205
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9206
|
+
}
|
|
9207
|
+
function computeTextFontSizeInPixels(style) {
|
|
9208
|
+
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
9209
|
+
}
|
|
9210
|
+
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
9211
|
+
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
9212
|
+
const splitWord = [];
|
|
9213
|
+
let wordPart = "";
|
|
9214
|
+
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
9215
|
+
splitWord.push(wordPart);
|
|
9216
|
+
wordPart = l;
|
|
9217
|
+
} else wordPart += l;
|
|
9218
|
+
splitWord.push(wordPart);
|
|
9219
|
+
return splitWord;
|
|
9220
|
+
}
|
|
9221
|
+
/**
|
|
9222
|
+
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
9223
|
+
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
9224
|
+
*/
|
|
9225
|
+
function splitTextToWidth(ctx, text, style, width) {
|
|
9226
|
+
if (!style) style = {};
|
|
9227
|
+
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
9228
|
+
const brokenText = [];
|
|
9229
|
+
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
9230
|
+
for (const line of lines) {
|
|
9231
|
+
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
9232
|
+
if (!width) {
|
|
9233
|
+
brokenText.push(line);
|
|
9234
|
+
continue;
|
|
9235
|
+
}
|
|
9236
|
+
let textLine = "";
|
|
9237
|
+
let availableWidth = width;
|
|
9238
|
+
for (const word of words) {
|
|
9239
|
+
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
9240
|
+
const lastPart = splitWord.pop();
|
|
9241
|
+
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
9242
|
+
if (splitWord.length) {
|
|
9243
|
+
if (textLine !== "") {
|
|
9244
|
+
brokenText.push(textLine);
|
|
9245
|
+
textLine = "";
|
|
9246
|
+
availableWidth = width;
|
|
9247
|
+
}
|
|
9248
|
+
splitWord.forEach((wordPart) => {
|
|
9249
|
+
brokenText.push(wordPart);
|
|
9250
|
+
});
|
|
9251
|
+
textLine = lastPart;
|
|
9252
|
+
availableWidth = width - lastPartWidth;
|
|
9253
|
+
} else {
|
|
9254
|
+
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
9255
|
+
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
9256
|
+
if (wordWidth <= availableWidth) {
|
|
9257
|
+
textLine += _word;
|
|
9258
|
+
availableWidth -= wordWidth;
|
|
9259
|
+
} else {
|
|
9260
|
+
brokenText.push(textLine);
|
|
9261
|
+
textLine = lastPart;
|
|
9262
|
+
availableWidth = width - lastPartWidth;
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
}
|
|
9266
|
+
if (textLine !== "") brokenText.push(textLine);
|
|
9267
|
+
}
|
|
9268
|
+
return brokenText;
|
|
9269
|
+
}
|
|
9270
|
+
/**
|
|
9271
|
+
* Return the font size that makes the width of a text match the given line width.
|
|
9272
|
+
* Minimum font size is 1.
|
|
9273
|
+
*
|
|
9274
|
+
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
9275
|
+
*/
|
|
9276
|
+
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
9277
|
+
let minFontSize = 1;
|
|
9278
|
+
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
9279
|
+
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
9280
|
+
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
9281
|
+
let currentTextWidth = getTextWidth(fontSize);
|
|
9282
|
+
let iterations = 0;
|
|
9283
|
+
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
9284
|
+
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
9285
|
+
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
9286
|
+
fontSize = (minFontSize + maxFontSize) / 2;
|
|
9287
|
+
currentTextWidth = getTextWidth(fontSize);
|
|
9288
|
+
iterations++;
|
|
9289
|
+
}
|
|
9290
|
+
return fontSize;
|
|
9291
|
+
}
|
|
9292
|
+
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
9293
|
+
function toTrimmedLowerCase(str) {
|
|
9294
|
+
return str ? str.toLowerCase().trim() : "";
|
|
9506
9295
|
}
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9296
|
+
/**
|
|
9297
|
+
* Extract the fontSize from a context font string
|
|
9298
|
+
* @param font The (context) font string to parse
|
|
9299
|
+
* @returns The fontSize in pixels
|
|
9300
|
+
*/
|
|
9301
|
+
const pxRegex = /([0-9\.]*)px/;
|
|
9302
|
+
function getContextFontSize(font) {
|
|
9303
|
+
return Number(font.match(pxRegex)?.[1]);
|
|
9512
9304
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9305
|
+
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
9306
|
+
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9307
|
+
if (width <= maxWidth) return text;
|
|
9308
|
+
const ellipsis = "…";
|
|
9309
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
9310
|
+
if (width <= ellipsisWidth) return text;
|
|
9311
|
+
let len = text.length;
|
|
9312
|
+
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
9313
|
+
text = text.substring(0, len);
|
|
9314
|
+
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9520
9315
|
}
|
|
9521
|
-
|
|
9522
|
-
|
|
9316
|
+
return text + ellipsis;
|
|
9317
|
+
}
|
|
9318
|
+
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
|
|
9319
|
+
if (highlightText) {
|
|
9320
|
+
context.save();
|
|
9321
|
+
context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
|
|
9523
9322
|
}
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9323
|
+
context.fillText(text, position.x, position.y);
|
|
9324
|
+
if (!underline && !strikethrough) return;
|
|
9325
|
+
const measure = context.measureText(text);
|
|
9326
|
+
const textWidth = measure.width;
|
|
9327
|
+
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
9328
|
+
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9329
|
+
let { x, y } = position;
|
|
9330
|
+
let strikeY = y, underlineY = y;
|
|
9331
|
+
switch (context.textAlign) {
|
|
9332
|
+
case "center":
|
|
9333
|
+
x -= textWidth / 2;
|
|
9334
|
+
break;
|
|
9335
|
+
case "right":
|
|
9336
|
+
x -= textWidth;
|
|
9337
|
+
break;
|
|
9529
9338
|
}
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9339
|
+
switch (context.textBaseline) {
|
|
9340
|
+
case "top":
|
|
9341
|
+
underlineY += boxHeight - 2 * strokeWidth;
|
|
9342
|
+
strikeY += boxHeight / 2 - strokeWidth;
|
|
9343
|
+
break;
|
|
9344
|
+
case "middle":
|
|
9345
|
+
underlineY += boxHeight / 2 - strokeWidth;
|
|
9346
|
+
break;
|
|
9347
|
+
case "alphabetic":
|
|
9348
|
+
underlineY += 2 * strokeWidth;
|
|
9349
|
+
strikeY -= 3 * strokeWidth;
|
|
9350
|
+
break;
|
|
9351
|
+
case "bottom":
|
|
9352
|
+
underlineY = y;
|
|
9353
|
+
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
9354
|
+
break;
|
|
9536
9355
|
}
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9356
|
+
if (underline) {
|
|
9357
|
+
context.lineWidth = strokeWidth;
|
|
9358
|
+
context.strokeStyle = context.fillStyle;
|
|
9359
|
+
context.beginPath();
|
|
9360
|
+
context.moveTo(x, underlineY);
|
|
9361
|
+
context.lineTo(x + textWidth, underlineY);
|
|
9362
|
+
context.stroke();
|
|
9540
9363
|
}
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
purples: [
|
|
9569
|
-
"#fcfbfd",
|
|
9570
|
-
"#9e9ac8",
|
|
9571
|
-
"#3f007d"
|
|
9572
|
-
],
|
|
9573
|
-
viridis: [
|
|
9574
|
-
"#440154",
|
|
9575
|
-
"#21918c",
|
|
9576
|
-
"#fde725"
|
|
9577
|
-
],
|
|
9578
|
-
cividis: [
|
|
9579
|
-
"#00224e",
|
|
9580
|
-
"#7d7c78",
|
|
9581
|
-
"#fee838"
|
|
9582
|
-
],
|
|
9583
|
-
rainbow: [
|
|
9584
|
-
"#B41DB4",
|
|
9585
|
-
"#FFFF00",
|
|
9586
|
-
"#00FFFF"
|
|
9587
|
-
]
|
|
9588
|
-
};
|
|
9589
|
-
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9364
|
+
if (strikethrough) {
|
|
9365
|
+
context.lineWidth = strokeWidth;
|
|
9366
|
+
context.strokeStyle = context.fillStyle;
|
|
9367
|
+
context.beginPath();
|
|
9368
|
+
context.moveTo(x, strikeY);
|
|
9369
|
+
context.lineTo(x + textWidth, strikeY);
|
|
9370
|
+
context.stroke();
|
|
9371
|
+
}
|
|
9372
|
+
if (highlightText) context.restore();
|
|
9373
|
+
}
|
|
9374
|
+
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
9375
|
+
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
9376
|
+
const ellipsis = "...";
|
|
9377
|
+
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
9378
|
+
if (ellipsisWidth >= width) return "";
|
|
9379
|
+
let lowerBoundLen = 1;
|
|
9380
|
+
let upperBoundLen = text.length;
|
|
9381
|
+
let currentWidth;
|
|
9382
|
+
while (lowerBoundLen <= upperBoundLen) {
|
|
9383
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
9384
|
+
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
9385
|
+
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
9386
|
+
else lowerBoundLen = currentLen + 1;
|
|
9387
|
+
}
|
|
9388
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
9389
|
+
return slicedText ? slicedText + ellipsis : "";
|
|
9390
|
+
}
|
|
9590
9391
|
/**
|
|
9591
|
-
*
|
|
9592
|
-
*
|
|
9392
|
+
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
9393
|
+
* with to original's text vertical and horizontal alignment.
|
|
9593
9394
|
*/
|
|
9594
|
-
function
|
|
9595
|
-
if (
|
|
9596
|
-
|
|
9597
|
-
const
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9395
|
+
function computeRotationPosition(rect, style) {
|
|
9396
|
+
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
9397
|
+
let { x, y } = rect;
|
|
9398
|
+
const cos = Math.cos(-style.rotation);
|
|
9399
|
+
const sin = Math.sin(-style.rotation);
|
|
9400
|
+
const width = rect.textWidth - 2 * 4;
|
|
9401
|
+
const height = rect.textHeight;
|
|
9402
|
+
const center = style.align === "center";
|
|
9403
|
+
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
9404
|
+
const sh = sin * height;
|
|
9405
|
+
const sw = Math.abs(sin * width);
|
|
9406
|
+
const ch = cos * height;
|
|
9407
|
+
if (style.verticalAlign === "top") if (center) {
|
|
9408
|
+
y += sw / 2;
|
|
9409
|
+
x -= sh / 2;
|
|
9410
|
+
} else if (rotateTowardCellCenter) x -= sh;
|
|
9411
|
+
else y += sw;
|
|
9412
|
+
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
9413
|
+
y += height - ch;
|
|
9414
|
+
if (center) {
|
|
9415
|
+
y -= sw / 2;
|
|
9416
|
+
x -= sh / 2;
|
|
9417
|
+
} else if (rotateTowardCellCenter) {
|
|
9418
|
+
x -= sh;
|
|
9419
|
+
y -= sw;
|
|
9420
|
+
}
|
|
9421
|
+
} else if (center) {
|
|
9422
|
+
x -= sh / 2;
|
|
9423
|
+
y -= height / 2;
|
|
9424
|
+
if (rotateTowardCellCenter) y += sh;
|
|
9425
|
+
else y -= sh;
|
|
9426
|
+
} else if (rotateTowardCellCenter) {
|
|
9427
|
+
x -= sh;
|
|
9428
|
+
y -= sw / 2;
|
|
9429
|
+
} else y += sw / 2 + ch / 4;
|
|
9430
|
+
return {
|
|
9431
|
+
x: cos * x - sin * y,
|
|
9432
|
+
y: cos * y + sin * x
|
|
9433
|
+
};
|
|
9434
|
+
}
|
|
9435
|
+
|
|
9436
|
+
//#endregion
|
|
9437
|
+
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
9438
|
+
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
9439
|
+
const chartColorScalePlugin = {
|
|
9440
|
+
id: "chartColorScalePlugin",
|
|
9441
|
+
afterDatasetsDraw(chart, args, options) {
|
|
9442
|
+
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
9443
|
+
const ctx = chart.ctx;
|
|
9444
|
+
ctx.save();
|
|
9445
|
+
ctx.textAlign = "center";
|
|
9446
|
+
ctx.textBaseline = "middle";
|
|
9447
|
+
ctx.miterLimit = 1;
|
|
9448
|
+
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
9449
|
+
const gradientWidth = 10;
|
|
9450
|
+
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
9451
|
+
const gradientY = chart.chartArea.top;
|
|
9452
|
+
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
9453
|
+
const step = 1 / (options.colorScale.length - 1);
|
|
9454
|
+
options.colorScale.forEach((color, index) => {
|
|
9455
|
+
gradient.addColorStop(index * step, color);
|
|
9611
9456
|
});
|
|
9457
|
+
ctx.fillStyle = gradient;
|
|
9458
|
+
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
9459
|
+
ctx.fillStyle = options.fontColor ?? "black";
|
|
9460
|
+
ctx.font = getDefaultContextFont(12);
|
|
9461
|
+
ctx.textAlign = "left";
|
|
9462
|
+
let minValue = Math.round(options.minValue * 100) / 100;
|
|
9463
|
+
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
9464
|
+
if (options.minValue === options.maxValue) {
|
|
9465
|
+
minValue -= 1;
|
|
9466
|
+
maxValue += 1;
|
|
9467
|
+
}
|
|
9468
|
+
const formattedMaxValue = humanizeNumber({
|
|
9469
|
+
value: maxValue,
|
|
9470
|
+
format: void 0
|
|
9471
|
+
}, options.locale);
|
|
9472
|
+
const formattedMinValue = humanizeNumber({
|
|
9473
|
+
value: minValue,
|
|
9474
|
+
format: void 0
|
|
9475
|
+
}, options.locale);
|
|
9476
|
+
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
9477
|
+
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
9478
|
+
ctx.restore();
|
|
9612
9479
|
}
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9480
|
+
};
|
|
9481
|
+
|
|
9482
|
+
//#endregion
|
|
9483
|
+
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
9484
|
+
function getFunnelChartController() {
|
|
9485
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9486
|
+
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
9487
|
+
static id = "funnel";
|
|
9488
|
+
static defaults = {
|
|
9489
|
+
...globalThis.Chart?.BarController.defaults,
|
|
9490
|
+
dataElementType: "funnel",
|
|
9491
|
+
animation: { duration: (ctx) => {
|
|
9492
|
+
if (ctx.type !== "data") return 1e3;
|
|
9493
|
+
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
9494
|
+
} }
|
|
9495
|
+
};
|
|
9496
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9497
|
+
updateElements(rects, start, count, mode) {
|
|
9498
|
+
super.updateElements(rects, start, count, mode);
|
|
9499
|
+
for (let i = start; i < start + count; i++) {
|
|
9500
|
+
const rect = rects[i];
|
|
9501
|
+
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9617
9504
|
};
|
|
9618
9505
|
}
|
|
9619
|
-
function
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9506
|
+
function getFunnelChartElement() {
|
|
9507
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9508
|
+
/**
|
|
9509
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9510
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9511
|
+
*/
|
|
9512
|
+
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
9513
|
+
static id = "funnel";
|
|
9514
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9515
|
+
draw(ctx) {
|
|
9516
|
+
ctx.save();
|
|
9517
|
+
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
9518
|
+
"x",
|
|
9519
|
+
"y",
|
|
9520
|
+
"width",
|
|
9521
|
+
"height",
|
|
9522
|
+
"nextElement",
|
|
9523
|
+
"base",
|
|
9524
|
+
"options"
|
|
9525
|
+
]);
|
|
9526
|
+
const width = getElementWidth(this);
|
|
9527
|
+
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
9528
|
+
const startX = Math.min(x, base);
|
|
9529
|
+
const startY = y - height / 2;
|
|
9530
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9531
|
+
ctx.beginPath();
|
|
9532
|
+
ctx.moveTo(startX, startY);
|
|
9533
|
+
ctx.lineTo(startX + width, startY);
|
|
9534
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9535
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9536
|
+
ctx.closePath();
|
|
9537
|
+
ctx.fill();
|
|
9538
|
+
if (options.borderWidth) {
|
|
9539
|
+
ctx.strokeStyle = options.borderColor;
|
|
9540
|
+
ctx.lineWidth = options.borderWidth;
|
|
9541
|
+
ctx.stroke();
|
|
9542
|
+
}
|
|
9543
|
+
ctx.restore();
|
|
9544
|
+
}
|
|
9545
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9546
|
+
inRange(mouseX, mouseY) {
|
|
9547
|
+
const { x, y, height, nextElement, base } = this.getProps([
|
|
9548
|
+
"x",
|
|
9549
|
+
"y",
|
|
9550
|
+
"width",
|
|
9551
|
+
"height",
|
|
9552
|
+
"nextElement",
|
|
9553
|
+
"base",
|
|
9554
|
+
"options"
|
|
9555
|
+
]);
|
|
9556
|
+
const width = getElementWidth(this);
|
|
9557
|
+
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
9558
|
+
const startX = Math.min(x, base);
|
|
9559
|
+
const startY = y - height / 2;
|
|
9560
|
+
if (mouseY < startY || mouseY > startY + height) return false;
|
|
9561
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9562
|
+
const left = startX + offset * (mouseY - startY) / height;
|
|
9563
|
+
const right = startX + width - offset * (mouseY - startY) / height;
|
|
9564
|
+
if (mouseX < left || mouseX > right) return false;
|
|
9565
|
+
return true;
|
|
9566
|
+
}
|
|
9567
|
+
};
|
|
9629
9568
|
}
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9569
|
+
/**
|
|
9570
|
+
* Get an element width.
|
|
9571
|
+
*
|
|
9572
|
+
* The property width is undefined during animations, we need to compute it manually.
|
|
9573
|
+
*/
|
|
9574
|
+
function getElementWidth(element) {
|
|
9575
|
+
const { x, base } = element.getProps(["x", "base"]);
|
|
9576
|
+
return Math.max(x, base) - Math.min(x, base);
|
|
9636
9577
|
}
|
|
9578
|
+
/**
|
|
9579
|
+
* Position the tooltip inside the trapezoid.
|
|
9580
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9581
|
+
*/
|
|
9582
|
+
const funnelTooltipPositioner = function(elements) {
|
|
9583
|
+
if (!elements.length) return {
|
|
9584
|
+
x: 0,
|
|
9585
|
+
y: 0
|
|
9586
|
+
};
|
|
9587
|
+
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
9588
|
+
"x",
|
|
9589
|
+
"y",
|
|
9590
|
+
"width",
|
|
9591
|
+
"height",
|
|
9592
|
+
"base"
|
|
9593
|
+
]);
|
|
9594
|
+
const startX = Math.min(x, base);
|
|
9595
|
+
const startY = y - height / 2;
|
|
9596
|
+
return {
|
|
9597
|
+
x: startX + width * 2 / 3,
|
|
9598
|
+
y: startY + height / 2
|
|
9599
|
+
};
|
|
9600
|
+
};
|
|
9601
|
+
|
|
9602
|
+
//#endregion
|
|
9603
|
+
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
9604
|
+
const chartMinorGridPlugin = {
|
|
9605
|
+
id: "o-spreadsheet-minor-gridlines",
|
|
9606
|
+
beforeDatasetsDraw(chart) {
|
|
9607
|
+
const ctx = chart.ctx;
|
|
9608
|
+
const chartArea = chart.chartArea;
|
|
9609
|
+
if (!chartArea) return;
|
|
9610
|
+
for (const scaleId in chart.scales) {
|
|
9611
|
+
const scale = chart.scales[scaleId];
|
|
9612
|
+
const options = scale.options;
|
|
9613
|
+
const minor = options?.grid?.minor;
|
|
9614
|
+
if (!minor?.display) continue;
|
|
9615
|
+
const showMajorGrid = options?.grid?.display;
|
|
9616
|
+
const ticks = scale.ticks;
|
|
9617
|
+
if (!ticks || ticks.length < 2) continue;
|
|
9618
|
+
ctx.save();
|
|
9619
|
+
ctx.lineWidth = 1;
|
|
9620
|
+
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
9621
|
+
for (let i = 0; i < ticks.length - 1; i++) {
|
|
9622
|
+
const start = scale.getPixelForTick(i);
|
|
9623
|
+
const end = scale.getPixelForTick(i + 1);
|
|
9624
|
+
if (!isFinite(start) || !isFinite(end)) continue;
|
|
9625
|
+
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
9626
|
+
const ratio = j / 4;
|
|
9627
|
+
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
9628
|
+
ctx.beginPath();
|
|
9629
|
+
if (scale.isHorizontal()) {
|
|
9630
|
+
ctx.moveTo(position, chartArea.top);
|
|
9631
|
+
ctx.lineTo(position, chartArea.bottom);
|
|
9632
|
+
} else {
|
|
9633
|
+
ctx.moveTo(chartArea.left, position);
|
|
9634
|
+
ctx.lineTo(chartArea.right, position);
|
|
9635
|
+
}
|
|
9636
|
+
ctx.stroke();
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
ctx.restore();
|
|
9640
|
+
}
|
|
9641
|
+
}
|
|
9642
|
+
};
|
|
9637
9643
|
|
|
9638
9644
|
//#endregion
|
|
9639
9645
|
//#region src/xlsx/constants.ts
|
|
@@ -12073,11 +12079,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12073
12079
|
if (structure.baseline) {
|
|
12074
12080
|
ctx.font = structure.baseline.style.font;
|
|
12075
12081
|
ctx.fillStyle = structure.baseline.style.color;
|
|
12076
|
-
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
|
|
12082
|
+
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
|
|
12077
12083
|
}
|
|
12078
12084
|
if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
|
|
12079
12085
|
ctx.save();
|
|
12080
|
-
ctx.fillStyle = structure.baselineArrow.style.color;
|
|
12086
|
+
ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
|
|
12081
12087
|
ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
|
|
12082
12088
|
const ratio = structure.baselineArrow.style.size / 10;
|
|
12083
12089
|
ctx.scale(ratio, ratio);
|
|
@@ -12095,18 +12101,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12095
12101
|
const descr = structure.baselineDescr;
|
|
12096
12102
|
ctx.font = descr.style.font;
|
|
12097
12103
|
ctx.fillStyle = descr.style.color;
|
|
12098
|
-
ctx
|
|
12104
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
|
|
12099
12105
|
}
|
|
12100
12106
|
if (structure.key) {
|
|
12101
12107
|
ctx.font = structure.key.style.font;
|
|
12102
12108
|
ctx.fillStyle = structure.key.style.color;
|
|
12103
|
-
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
|
|
12109
|
+
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);
|
|
12104
12110
|
}
|
|
12105
12111
|
if (structure.keyDescr) {
|
|
12106
12112
|
const descr = structure.keyDescr;
|
|
12107
12113
|
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12108
12114
|
ctx.fillStyle = descr.style.color;
|
|
12109
|
-
ctx
|
|
12115
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
|
|
12110
12116
|
}
|
|
12111
12117
|
if (structure.progressBar) {
|
|
12112
12118
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
@@ -12330,29 +12336,34 @@ var ScorecardChartConfigBuilder = class {
|
|
|
12330
12336
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12331
12337
|
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12332
12338
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12333
|
-
underline: this.runtime.keyValueStyle?.underline
|
|
12339
|
+
underline: this.runtime.keyValueStyle?.underline,
|
|
12340
|
+
highlightText: this.runtime.keyHighlight
|
|
12334
12341
|
},
|
|
12335
12342
|
keyDescr: {
|
|
12336
12343
|
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12337
12344
|
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12338
12345
|
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12339
|
-
underline: this.runtime.keyValueDescrStyle?.underline
|
|
12346
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12347
|
+
highlightText: this.runtime.keyHighlight
|
|
12340
12348
|
},
|
|
12341
12349
|
baselineValue: {
|
|
12342
12350
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12343
12351
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12344
12352
|
underline: this.runtime.baselineStyle?.underline,
|
|
12345
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12353
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12354
|
+
highlightText: this.runtime.baselineHighlight
|
|
12346
12355
|
},
|
|
12347
12356
|
baselineDescr: {
|
|
12348
12357
|
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12349
12358
|
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12350
12359
|
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12351
|
-
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
|
|
12360
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12361
|
+
highlightText: this.runtime.baselineHighlight
|
|
12352
12362
|
},
|
|
12353
12363
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
|
|
12354
12364
|
size: this.keyValue ? .8 * baselineValueFontSize : 0,
|
|
12355
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12365
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12366
|
+
highlight: this.runtime.baselineHighlight
|
|
12356
12367
|
}
|
|
12357
12368
|
};
|
|
12358
12369
|
}
|
|
@@ -12448,11 +12459,14 @@ var ScorecardChart = class extends Component {
|
|
|
12448
12459
|
});
|
|
12449
12460
|
(0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
|
|
12450
12461
|
}
|
|
12462
|
+
config(canvasRect, zoom) {
|
|
12463
|
+
return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
|
|
12464
|
+
}
|
|
12451
12465
|
createChart() {
|
|
12452
12466
|
const canvas = this.canvas();
|
|
12453
12467
|
if (!canvas) return;
|
|
12454
12468
|
const zoom = this.env.model.getters.getViewportZoomLevel();
|
|
12455
|
-
drawScoreChart(
|
|
12469
|
+
drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
|
|
12456
12470
|
}
|
|
12457
12471
|
};
|
|
12458
12472
|
|
|
@@ -28056,7 +28070,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
|
|
|
28056
28070
|
*/
|
|
28057
28071
|
function filterInvalidHierarchicalPoints(values, hierarchy) {
|
|
28058
28072
|
const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
|
|
28059
|
-
const isEmpty = (value) => value === null || value === "";
|
|
28073
|
+
const isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
28060
28074
|
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
|
|
28061
28075
|
const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
|
|
28062
28076
|
if (isEmpty(groups[0]?.value)) return false;
|
|
@@ -34537,6 +34551,35 @@ var CellIsRuleEditor = class extends Component {
|
|
|
34537
34551
|
}
|
|
34538
34552
|
};
|
|
34539
34553
|
|
|
34554
|
+
//#endregion
|
|
34555
|
+
//#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
|
|
34556
|
+
var CalendarButton = class extends Component {
|
|
34557
|
+
static template = "o-spreadsheet-CalendarButton";
|
|
34558
|
+
props = (0, _odoo_owl.props)({
|
|
34559
|
+
value: types.string().optional(""),
|
|
34560
|
+
onChange: types.function()
|
|
34561
|
+
});
|
|
34562
|
+
datePickerRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
34563
|
+
openCalendar() {
|
|
34564
|
+
this.datePickerRef()?.showPicker();
|
|
34565
|
+
}
|
|
34566
|
+
formatDateForInput(value) {
|
|
34567
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34568
|
+
return dateValue ? formatValue(dateValue.value, {
|
|
34569
|
+
format: "yyyy-mm-dd",
|
|
34570
|
+
locale: DEFAULT_LOCALE
|
|
34571
|
+
}) : "";
|
|
34572
|
+
}
|
|
34573
|
+
onDateInputValueChanged(value) {
|
|
34574
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34575
|
+
const formattedValue = dateValue ? formatValue(dateValue.value, {
|
|
34576
|
+
format: DEFAULT_LOCALE.dateFormat,
|
|
34577
|
+
locale: DEFAULT_LOCALE
|
|
34578
|
+
}) : "";
|
|
34579
|
+
this.props.onChange(formattedValue);
|
|
34580
|
+
}
|
|
34581
|
+
};
|
|
34582
|
+
|
|
34540
34583
|
//#endregion
|
|
34541
34584
|
//#region src/components/side_panel/criterion_form/criterion_form.ts
|
|
34542
34585
|
var CriterionForm = class extends Component {
|
|
@@ -35225,7 +35268,8 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35225
35268
|
static template = "o-spreadsheet-DataValidationDateCriterion";
|
|
35226
35269
|
static components = {
|
|
35227
35270
|
CriterionInput,
|
|
35228
|
-
Select
|
|
35271
|
+
Select,
|
|
35272
|
+
CalendarButton
|
|
35229
35273
|
};
|
|
35230
35274
|
get currentDateValue() {
|
|
35231
35275
|
return this.props.criterion.dateValue || "exactDate";
|
|
@@ -35251,7 +35295,10 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35251
35295
|
//#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
|
|
35252
35296
|
var DoubleInputCriterionForm = class extends CriterionForm {
|
|
35253
35297
|
static template = "o-spreadsheet-DoubleInputCriterionForm";
|
|
35254
|
-
static components = {
|
|
35298
|
+
static components = {
|
|
35299
|
+
CriterionInput,
|
|
35300
|
+
CalendarButton
|
|
35301
|
+
};
|
|
35255
35302
|
onFirstValueChanged(value) {
|
|
35256
35303
|
const values = this.props.criterion.values;
|
|
35257
35304
|
this.updateCriterion({ values: [value, values[1] || ""] });
|
|
@@ -35260,6 +35307,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
|
|
|
35260
35307
|
const values = this.props.criterion.values;
|
|
35261
35308
|
this.updateCriterion({ values: [values[0] || "", value] });
|
|
35262
35309
|
}
|
|
35310
|
+
get isDateType() {
|
|
35311
|
+
return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
|
|
35312
|
+
}
|
|
35263
35313
|
};
|
|
35264
35314
|
|
|
35265
35315
|
//#endregion
|
|
@@ -46606,7 +46656,7 @@ var FiguresContainer = class extends Component {
|
|
|
46606
46656
|
horizontalSnap: void 0,
|
|
46607
46657
|
verticalSnap: void 0,
|
|
46608
46658
|
cancelDnd: void 0,
|
|
46609
|
-
|
|
46659
|
+
overlappingChartOrCarousel: void 0
|
|
46610
46660
|
});
|
|
46611
46661
|
setup() {
|
|
46612
46662
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -46622,7 +46672,7 @@ var FiguresContainer = class extends Component {
|
|
|
46622
46672
|
this.dnd.selectedRect = void 0;
|
|
46623
46673
|
this.dnd.horizontalSnap = void 0;
|
|
46624
46674
|
this.dnd.verticalSnap = void 0;
|
|
46625
|
-
this.dnd.
|
|
46675
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46626
46676
|
this.dnd.cancelDnd = void 0;
|
|
46627
46677
|
}
|
|
46628
46678
|
});
|
|
@@ -46760,11 +46810,11 @@ var FiguresContainer = class extends Component {
|
|
|
46760
46810
|
hasStartedDnd = true;
|
|
46761
46811
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
|
|
46762
46812
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
46763
|
-
let
|
|
46813
|
+
let overlappingChartOrCarousel = void 0;
|
|
46764
46814
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
46765
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart"))
|
|
46766
|
-
this.dnd.
|
|
46767
|
-
if (!
|
|
46815
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
|
|
46816
|
+
this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
|
|
46817
|
+
if (!overlappingChartOrCarousel) {
|
|
46768
46818
|
const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
|
|
46769
46819
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
46770
46820
|
this.dnd.selectedRect = this.getDndFigureRect();
|
|
@@ -46785,7 +46835,7 @@ var FiguresContainer = class extends Component {
|
|
|
46785
46835
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
46786
46836
|
return;
|
|
46787
46837
|
}
|
|
46788
|
-
if (!this.dnd.
|
|
46838
|
+
if (!this.dnd.overlappingChartOrCarousel) {
|
|
46789
46839
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
46790
46840
|
return {
|
|
46791
46841
|
sheetId,
|
|
@@ -46795,20 +46845,25 @@ var FiguresContainer = class extends Component {
|
|
|
46795
46845
|
}) || [];
|
|
46796
46846
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
46797
46847
|
} else {
|
|
46798
|
-
const
|
|
46848
|
+
const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
|
|
46799
46849
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
46800
|
-
this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46850
|
+
if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46801
46851
|
sheetId,
|
|
46802
|
-
carouselFigureId,
|
|
46852
|
+
carouselFigureId: overlappingFigureId,
|
|
46803
46853
|
chartFigureIds
|
|
46804
46854
|
});
|
|
46855
|
+
else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
46856
|
+
sheetId,
|
|
46857
|
+
baseFigureId: overlappingFigureId,
|
|
46858
|
+
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
46859
|
+
});
|
|
46805
46860
|
}
|
|
46806
46861
|
this.dnd.draggedFigure = void 0;
|
|
46807
46862
|
this.dnd.selectedFigures = void 0;
|
|
46808
46863
|
this.dnd.selectedRect = void 0;
|
|
46809
46864
|
this.dnd.horizontalSnap = void 0;
|
|
46810
46865
|
this.dnd.verticalSnap = void 0;
|
|
46811
|
-
this.dnd.
|
|
46866
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46812
46867
|
};
|
|
46813
46868
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46814
46869
|
}
|
|
@@ -46864,7 +46919,7 @@ var FiguresContainer = class extends Component {
|
|
|
46864
46919
|
this.dnd.selectedRect = void 0;
|
|
46865
46920
|
this.dnd.horizontalSnap = void 0;
|
|
46866
46921
|
this.dnd.verticalSnap = void 0;
|
|
46867
|
-
this.dnd.
|
|
46922
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46868
46923
|
};
|
|
46869
46924
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46870
46925
|
}
|
|
@@ -46874,12 +46929,12 @@ var FiguresContainer = class extends Component {
|
|
|
46874
46929
|
getFigureStyle(figureUI) {
|
|
46875
46930
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
46876
46931
|
return cssPropertiesToCss({
|
|
46877
|
-
opacity: this.dnd.
|
|
46932
|
+
opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
|
|
46878
46933
|
cursor: "grabbing"
|
|
46879
46934
|
});
|
|
46880
46935
|
}
|
|
46881
46936
|
getFigureClass(figureUI) {
|
|
46882
|
-
if (figureUI.id !== this.dnd.
|
|
46937
|
+
if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
|
|
46883
46938
|
return "o-add-to-carousel";
|
|
46884
46939
|
}
|
|
46885
46940
|
getSnap(snapLine) {
|
|
@@ -46922,18 +46977,18 @@ var FiguresContainer = class extends Component {
|
|
|
46922
46977
|
height: `100%`
|
|
46923
46978
|
});
|
|
46924
46979
|
}
|
|
46925
|
-
|
|
46980
|
+
getOverlappingFigure(figureUI, otherFigures) {
|
|
46926
46981
|
if (figureUI.tag !== "chart") return;
|
|
46927
46982
|
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
46928
46983
|
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
46929
46984
|
let bestMatch;
|
|
46930
46985
|
let smallestDistance = Infinity;
|
|
46931
46986
|
for (const figure of otherFigures) {
|
|
46932
|
-
if (figure.tag !== "carousel") continue;
|
|
46933
|
-
const
|
|
46934
|
-
const
|
|
46935
|
-
const distanceX = Math.abs(figureCenterX -
|
|
46936
|
-
const distanceY = Math.abs(figureCenterY -
|
|
46987
|
+
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
46988
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
46989
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
46990
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
46991
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
46937
46992
|
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
46938
46993
|
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
46939
46994
|
smallestDistance = squaredDistance;
|
|
@@ -58904,13 +58959,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
|
|
|
58904
58959
|
const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
|
|
58905
58960
|
const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
|
|
58906
58961
|
const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
|
|
58907
|
-
if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58962
|
+
if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58908
58963
|
const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
|
|
58909
58964
|
const colorThresholds = [{
|
|
58910
58965
|
value: minValue,
|
|
58911
58966
|
color: rule.minimum.color
|
|
58912
58967
|
}];
|
|
58913
|
-
if (rule.midpoint && midValue) colorThresholds.push({
|
|
58968
|
+
if (rule.midpoint && midValue !== null) colorThresholds.push({
|
|
58914
58969
|
value: midValue,
|
|
58915
58970
|
color: rule.midpoint.color
|
|
58916
58971
|
});
|
|
@@ -66369,7 +66424,9 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66369
66424
|
ctx.lineWidth = 1.5 * thinLineWidth;
|
|
66370
66425
|
const isDarkMode = this.getters.isDarkMode();
|
|
66371
66426
|
for (const zone of zones) {
|
|
66427
|
+
if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
|
|
66372
66428
|
const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
|
|
66429
|
+
const currentLineWidth = ctx.lineWidth;
|
|
66373
66430
|
if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
|
|
66374
66431
|
if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
|
|
66375
66432
|
if (height === 0 && width === 0) {
|
|
@@ -66383,6 +66440,7 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66383
66440
|
ctx.globalCompositeOperation = "source-over";
|
|
66384
66441
|
ctx.strokeRect(x, y, width, height);
|
|
66385
66442
|
}
|
|
66443
|
+
ctx.lineWidth = currentLineWidth;
|
|
66386
66444
|
}
|
|
66387
66445
|
ctx.globalCompositeOperation = "source-over";
|
|
66388
66446
|
const position = renderingContext.activePosition;
|
|
@@ -66529,6 +66587,9 @@ var InternalViewport = class {
|
|
|
66529
66587
|
this.adjustViewportZoneX();
|
|
66530
66588
|
this.adjustViewportZoneY();
|
|
66531
66589
|
}
|
|
66590
|
+
isZoneVisible(zone) {
|
|
66591
|
+
return intersection(zone, this) !== void 0;
|
|
66592
|
+
}
|
|
66532
66593
|
/**
|
|
66533
66594
|
*
|
|
66534
66595
|
* Computes the visible coordinates & dimensions of a given zone inside the viewport
|
|
@@ -66809,6 +66870,9 @@ var ViewportCollection = class {
|
|
|
66809
66870
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
66810
66871
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
66811
66872
|
}
|
|
66873
|
+
isZoneVisibleInViewport(sheetId, zone) {
|
|
66874
|
+
return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
|
|
66875
|
+
}
|
|
66812
66876
|
getScrollBarWidth() {
|
|
66813
66877
|
return 15 / this.zoomLevel;
|
|
66814
66878
|
}
|
|
@@ -70882,18 +70946,28 @@ var NamedRangeSelector = class extends Component {
|
|
|
70882
70946
|
return;
|
|
70883
70947
|
}
|
|
70884
70948
|
const activeSheetId = this.env.model.getters.getActiveSheetId();
|
|
70885
|
-
if (activeSheetId !== sheetId)
|
|
70886
|
-
|
|
70887
|
-
|
|
70888
|
-
|
|
70889
|
-
|
|
70949
|
+
if (activeSheetId !== sheetId) {
|
|
70950
|
+
if (!this.env.model.getters.getSheet(sheetId).isVisible) {
|
|
70951
|
+
this.env.notifyUser({
|
|
70952
|
+
text: _t("The sheet on which the range is defined is hidden."),
|
|
70953
|
+
type: "info",
|
|
70954
|
+
sticky: false
|
|
70955
|
+
});
|
|
70956
|
+
return;
|
|
70957
|
+
}
|
|
70958
|
+
this.env.model.dispatch("ACTIVATE_SHEET", {
|
|
70959
|
+
sheetIdFrom: activeSheetId,
|
|
70960
|
+
sheetIdTo: sheetId
|
|
70961
|
+
});
|
|
70962
|
+
}
|
|
70963
|
+
this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
|
|
70890
70964
|
this.env.model.selection.selectZone({
|
|
70891
70965
|
cell: {
|
|
70892
70966
|
col: zone.left,
|
|
70893
70967
|
row: zone.top
|
|
70894
70968
|
},
|
|
70895
70969
|
zone
|
|
70896
|
-
});
|
|
70970
|
+
}, { allowsHiddenSelection: true });
|
|
70897
70971
|
}
|
|
70898
70972
|
get selectionKey() {
|
|
70899
70973
|
return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
|
|
@@ -87518,7 +87592,9 @@ const helpers = {
|
|
|
87518
87592
|
collapseHierarchicalDisplayName,
|
|
87519
87593
|
getCanonicalSymbolName,
|
|
87520
87594
|
fuzzyLookup,
|
|
87521
|
-
replaceSymbolInFormula
|
|
87595
|
+
replaceSymbolInFormula,
|
|
87596
|
+
isSingleCellReference,
|
|
87597
|
+
computeCachedTextDimension
|
|
87522
87598
|
};
|
|
87523
87599
|
const links = {
|
|
87524
87600
|
isMarkdownLink,
|
|
@@ -87588,7 +87664,8 @@ const components = {
|
|
|
87588
87664
|
FullScreenFigure,
|
|
87589
87665
|
NumberInput,
|
|
87590
87666
|
TopBar,
|
|
87591
|
-
Composer
|
|
87667
|
+
Composer,
|
|
87668
|
+
CalendarButton
|
|
87592
87669
|
};
|
|
87593
87670
|
const hooks = {
|
|
87594
87671
|
useDragAndDropListItems,
|
|
@@ -87742,6 +87819,6 @@ exports.stores = stores;
|
|
|
87742
87819
|
exports.tokenColors = tokenColors;
|
|
87743
87820
|
exports.tokenize = tokenize;
|
|
87744
87821
|
|
|
87745
|
-
__info__.version = "19.5.0-alpha.
|
|
87746
|
-
__info__.date = "2026-07-
|
|
87747
|
-
__info__.hash = "
|
|
87822
|
+
__info__.version = "19.5.0-alpha.2";
|
|
87823
|
+
__info__.date = "2026-07-13T06:26:20.354Z";
|
|
87824
|
+
__info__.hash = "a4d2f90";
|