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