@odoo/o-spreadsheet 19.5.0-alpha.1 → 19.5.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o_spreadsheet.cjs +1047 -846
- package/dist/o_spreadsheet.css +22 -5
- package/dist/o_spreadsheet.esm.js +1045 -844
- package/dist/o_spreadsheet.iife.js +1047 -846
- package/dist/o_spreadsheet.min.iife.js +266 -266
- package/dist/o_spreadsheet.xml +59 -26
- package/dist/types/clipboard_handlers/default_clipboard.d.ts +1 -1
- package/dist/types/components/figures/chart/scorecard/chart_scorecard.d.ts +2 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +2 -2
- package/dist/types/components/side_panel/criterion_form/calendar_button/calendar_button.d.ts +13 -0
- package/dist/types/components/side_panel/criterion_form/date_criterion/date_criterion.d.ts +2 -0
- package/dist/types/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.d.ts +3 -0
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/helpers/figures/charts/scorecard_chart_config_builder.d.ts +2 -0
- package/dist/types/helpers/internal_viewport.d.ts +1 -0
- package/dist/types/helpers/misc.d.ts +1 -0
- package/dist/types/helpers/text_helper.d.ts +6 -1
- package/dist/types/helpers/viewport_collection.d.ts +1 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/types/chart/radar_chart.d.ts +1 -1
- package/dist/types/types/chart/scorecard_chart.d.ts +2 -0
- package/dist/types/types/misc.d.ts +4 -0
- package/package.json +1 -1
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.3
|
|
6
|
+
* @date 2026-07-14T10:17:06.949Z
|
|
7
|
+
* @hash c029184
|
|
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 = {
|
|
@@ -1247,6 +1248,12 @@ function defaultDict(def) {
|
|
|
1247
1248
|
}
|
|
1248
1249
|
};
|
|
1249
1250
|
}
|
|
1251
|
+
function repeat(array, times) {
|
|
1252
|
+
const len = array.length;
|
|
1253
|
+
const result = new Array(len * times);
|
|
1254
|
+
for (let t = 0; t < times; t++) for (let i = 0; i < len; i++) result[t * len + i] = array[i];
|
|
1255
|
+
return result;
|
|
1256
|
+
}
|
|
1250
1257
|
|
|
1251
1258
|
//#endregion
|
|
1252
1259
|
//#region src/helpers/coordinates.ts
|
|
@@ -2600,7 +2607,7 @@ function unregisterChartJsExtensions() {
|
|
|
2600
2607
|
*
|
|
2601
2608
|
* The end goal is to eliminate all compatibility shims.
|
|
2602
2609
|
*/
|
|
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$
|
|
2610
|
+
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
2611
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2605
2612
|
var _Component = class extends OwlComponent {
|
|
2606
2613
|
static template = "";
|
|
@@ -2730,7 +2737,7 @@ var Portal = class extends OwlComponent {
|
|
|
2730
2737
|
static template = xml$4`<t t-call-slot="default"/>`;
|
|
2731
2738
|
constructor(node) {
|
|
2732
2739
|
super(node);
|
|
2733
|
-
this.props = props$
|
|
2740
|
+
this.props = props$167();
|
|
2734
2741
|
this.__owl__ = node;
|
|
2735
2742
|
}
|
|
2736
2743
|
setup() {
|
|
@@ -8406,534 +8413,6 @@ function addOrigin(cell, origin) {
|
|
|
8406
8413
|
return cell;
|
|
8407
8414
|
}
|
|
8408
8415
|
|
|
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
8416
|
//#endregion
|
|
8938
8417
|
//#region src/helpers/color.ts
|
|
8939
8418
|
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
|
|
@@ -9527,113 +9006,646 @@ var AlternatingColorGenerator = class extends ColorGenerator {
|
|
|
9527
9006
|
super(paletteSize, preferredColors);
|
|
9528
9007
|
this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
9529
9008
|
}
|
|
9530
|
-
};
|
|
9531
|
-
var AlternatingColorMap = class {
|
|
9532
|
-
availableColors;
|
|
9533
|
-
colors = {};
|
|
9534
|
-
constructor(paletteSize = 12) {
|
|
9535
|
-
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9009
|
+
};
|
|
9010
|
+
var AlternatingColorMap = class {
|
|
9011
|
+
availableColors;
|
|
9012
|
+
colors = {};
|
|
9013
|
+
constructor(paletteSize = 12) {
|
|
9014
|
+
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9015
|
+
}
|
|
9016
|
+
get(id) {
|
|
9017
|
+
if (!this.colors[id]) this.colors[id] = this.availableColors.next();
|
|
9018
|
+
return this.colors[id];
|
|
9019
|
+
}
|
|
9020
|
+
};
|
|
9021
|
+
const COLORSCHEMES = {
|
|
9022
|
+
greys: [
|
|
9023
|
+
"#ffffff",
|
|
9024
|
+
"#808080",
|
|
9025
|
+
"#000000"
|
|
9026
|
+
],
|
|
9027
|
+
blues: [
|
|
9028
|
+
"#f7fbff",
|
|
9029
|
+
"#6aaed6",
|
|
9030
|
+
"#08306b"
|
|
9031
|
+
],
|
|
9032
|
+
reds: [
|
|
9033
|
+
"#fff5f0",
|
|
9034
|
+
"#fb694a",
|
|
9035
|
+
"#67000d"
|
|
9036
|
+
],
|
|
9037
|
+
greens: [
|
|
9038
|
+
"#f7fcf5",
|
|
9039
|
+
"#73c476",
|
|
9040
|
+
"#00441b"
|
|
9041
|
+
],
|
|
9042
|
+
oranges: [
|
|
9043
|
+
"#fff5eb",
|
|
9044
|
+
"#fd8c3b",
|
|
9045
|
+
"#7f2704"
|
|
9046
|
+
],
|
|
9047
|
+
purples: [
|
|
9048
|
+
"#fcfbfd",
|
|
9049
|
+
"#9e9ac8",
|
|
9050
|
+
"#3f007d"
|
|
9051
|
+
],
|
|
9052
|
+
viridis: [
|
|
9053
|
+
"#440154",
|
|
9054
|
+
"#21918c",
|
|
9055
|
+
"#fde725"
|
|
9056
|
+
],
|
|
9057
|
+
cividis: [
|
|
9058
|
+
"#00224e",
|
|
9059
|
+
"#7d7c78",
|
|
9060
|
+
"#fee838"
|
|
9061
|
+
],
|
|
9062
|
+
rainbow: [
|
|
9063
|
+
"#B41DB4",
|
|
9064
|
+
"#FFFF00",
|
|
9065
|
+
"#00FFFF"
|
|
9066
|
+
]
|
|
9067
|
+
};
|
|
9068
|
+
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9069
|
+
/**
|
|
9070
|
+
* Returns a function that maps a value to a color using a color scale defined by the given
|
|
9071
|
+
* color/threshold values pairs.
|
|
9072
|
+
*/
|
|
9073
|
+
function getColorScale(colorScalePoints) {
|
|
9074
|
+
if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
|
|
9075
|
+
const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
|
|
9076
|
+
const thresholds = [];
|
|
9077
|
+
for (let i = 1; i < sortedColorScalePoints.length; i++) {
|
|
9078
|
+
const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
|
|
9079
|
+
const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
|
|
9080
|
+
const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
|
|
9081
|
+
const maxColor = colorToNumber(sortedColorScalePoints[i].color);
|
|
9082
|
+
thresholds.push({
|
|
9083
|
+
min: sortedColorScalePoints[i - 1].value,
|
|
9084
|
+
max: sortedColorScalePoints[i].value,
|
|
9085
|
+
minColor,
|
|
9086
|
+
maxColor,
|
|
9087
|
+
minColorAlpha,
|
|
9088
|
+
maxColorAlpha,
|
|
9089
|
+
colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
|
|
9090
|
+
});
|
|
9091
|
+
}
|
|
9092
|
+
return (value) => {
|
|
9093
|
+
if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
|
|
9094
|
+
for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
|
|
9095
|
+
return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
|
|
9096
|
+
};
|
|
9097
|
+
}
|
|
9098
|
+
function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
|
|
9099
|
+
const deltaValue = maxValue - minValue;
|
|
9100
|
+
const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
|
|
9101
|
+
const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
|
|
9102
|
+
const deltaColorB = minColor % 256 - maxColor % 256;
|
|
9103
|
+
return [
|
|
9104
|
+
deltaColorR / deltaValue,
|
|
9105
|
+
deltaColorG / deltaValue,
|
|
9106
|
+
deltaColorB / deltaValue
|
|
9107
|
+
];
|
|
9108
|
+
}
|
|
9109
|
+
function colorCell(value, minValue, minColor, colorDiffUnit) {
|
|
9110
|
+
const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
|
|
9111
|
+
const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
|
|
9112
|
+
const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
|
|
9113
|
+
const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
|
|
9114
|
+
return r << 16 | g << 8 | b;
|
|
9115
|
+
}
|
|
9116
|
+
|
|
9117
|
+
//#endregion
|
|
9118
|
+
//#region src/helpers/text_helper.ts
|
|
9119
|
+
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
9120
|
+
return numberOfLines * (textLineHeight + 4) - 4;
|
|
9121
|
+
}
|
|
9122
|
+
function getCanvas(width = 100, height = 100) {
|
|
9123
|
+
return new OffscreenCanvas(width, height).getContext("2d");
|
|
9124
|
+
}
|
|
9125
|
+
/**
|
|
9126
|
+
* Get the default height of the cell given its style.
|
|
9127
|
+
*/
|
|
9128
|
+
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
9129
|
+
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
9130
|
+
let content = "";
|
|
9131
|
+
try {
|
|
9132
|
+
if (!cell.isFormula) {
|
|
9133
|
+
const localeFormat = {
|
|
9134
|
+
format: cell.format,
|
|
9135
|
+
locale
|
|
9136
|
+
};
|
|
9137
|
+
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
9138
|
+
}
|
|
9139
|
+
} catch {
|
|
9140
|
+
content = CellErrorType.GenericError;
|
|
9141
|
+
}
|
|
9142
|
+
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
9143
|
+
}
|
|
9144
|
+
function getCellContentHeight(ctx, content, style, colSize) {
|
|
9145
|
+
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
9146
|
+
}
|
|
9147
|
+
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
9148
|
+
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
9149
|
+
}
|
|
9150
|
+
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
9151
|
+
if (!textLines.length) return {
|
|
9152
|
+
width: 0,
|
|
9153
|
+
height: 0
|
|
9154
|
+
};
|
|
9155
|
+
const font = computeTextFont(style, fontUnit);
|
|
9156
|
+
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
9157
|
+
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
9158
|
+
const width = Math.max(...sizes.map((size) => size.width));
|
|
9159
|
+
if (!style.rotation) return {
|
|
9160
|
+
height,
|
|
9161
|
+
width
|
|
9162
|
+
};
|
|
9163
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9164
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9165
|
+
return {
|
|
9166
|
+
width: width * cos + height * sin,
|
|
9167
|
+
height: sin * width + cos * height
|
|
9168
|
+
};
|
|
9169
|
+
}
|
|
9170
|
+
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
9171
|
+
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
9172
|
+
}
|
|
9173
|
+
function computeCachedTextWidth(context, text, font, rotation) {
|
|
9174
|
+
const size = computeCachedTextDimension(context, text, font);
|
|
9175
|
+
if (!rotation) return size.width;
|
|
9176
|
+
const cos = Math.abs(Math.cos(rotation));
|
|
9177
|
+
const sin = Math.abs(Math.sin(rotation));
|
|
9178
|
+
return size.width * cos + size.height * sin;
|
|
9179
|
+
}
|
|
9180
|
+
const textDimensionsCache = {};
|
|
9181
|
+
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
9182
|
+
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
9183
|
+
if (!style.rotation) return size;
|
|
9184
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9185
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9186
|
+
return {
|
|
9187
|
+
width: size.width * cos + size.height * sin,
|
|
9188
|
+
height: size.height * cos + size.width * sin
|
|
9189
|
+
};
|
|
9190
|
+
}
|
|
9191
|
+
function computeCachedTextDimension(context, text, font) {
|
|
9192
|
+
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
9193
|
+
if (textDimensionsCache[font][text] === void 0) {
|
|
9194
|
+
context.save();
|
|
9195
|
+
context.font = font;
|
|
9196
|
+
const measure = context.measureText(text);
|
|
9197
|
+
context.restore();
|
|
9198
|
+
const width = measure.width;
|
|
9199
|
+
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9200
|
+
textDimensionsCache[font][text] = {
|
|
9201
|
+
width,
|
|
9202
|
+
height
|
|
9203
|
+
};
|
|
9204
|
+
}
|
|
9205
|
+
return textDimensionsCache[font][text];
|
|
9206
|
+
}
|
|
9207
|
+
function fontSizeInPixels(fontSize) {
|
|
9208
|
+
return Math.round(fontSize * 96 / 72);
|
|
9209
|
+
}
|
|
9210
|
+
function computeTextFont(style, fontUnit = "pt") {
|
|
9211
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9212
|
+
}
|
|
9213
|
+
function computeTextFontSizeInPixels(style) {
|
|
9214
|
+
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
9215
|
+
}
|
|
9216
|
+
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
9217
|
+
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
9218
|
+
const splitWord = [];
|
|
9219
|
+
let wordPart = "";
|
|
9220
|
+
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
9221
|
+
splitWord.push(wordPart);
|
|
9222
|
+
wordPart = l;
|
|
9223
|
+
} else wordPart += l;
|
|
9224
|
+
splitWord.push(wordPart);
|
|
9225
|
+
return splitWord;
|
|
9226
|
+
}
|
|
9227
|
+
/**
|
|
9228
|
+
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
9229
|
+
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
9230
|
+
*/
|
|
9231
|
+
function splitTextToWidth(ctx, text, style, width) {
|
|
9232
|
+
if (!style) style = {};
|
|
9233
|
+
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
9234
|
+
const brokenText = [];
|
|
9235
|
+
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
9236
|
+
for (const line of lines) {
|
|
9237
|
+
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
9238
|
+
if (!width) {
|
|
9239
|
+
brokenText.push(line);
|
|
9240
|
+
continue;
|
|
9241
|
+
}
|
|
9242
|
+
let textLine = "";
|
|
9243
|
+
let availableWidth = width;
|
|
9244
|
+
for (const word of words) {
|
|
9245
|
+
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
9246
|
+
const lastPart = splitWord.pop();
|
|
9247
|
+
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
9248
|
+
if (splitWord.length) {
|
|
9249
|
+
if (textLine !== "") {
|
|
9250
|
+
brokenText.push(textLine);
|
|
9251
|
+
textLine = "";
|
|
9252
|
+
availableWidth = width;
|
|
9253
|
+
}
|
|
9254
|
+
splitWord.forEach((wordPart) => {
|
|
9255
|
+
brokenText.push(wordPart);
|
|
9256
|
+
});
|
|
9257
|
+
textLine = lastPart;
|
|
9258
|
+
availableWidth = width - lastPartWidth;
|
|
9259
|
+
} else {
|
|
9260
|
+
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
9261
|
+
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
9262
|
+
if (wordWidth <= availableWidth) {
|
|
9263
|
+
textLine += _word;
|
|
9264
|
+
availableWidth -= wordWidth;
|
|
9265
|
+
} else {
|
|
9266
|
+
brokenText.push(textLine);
|
|
9267
|
+
textLine = lastPart;
|
|
9268
|
+
availableWidth = width - lastPartWidth;
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
}
|
|
9272
|
+
if (textLine !== "") brokenText.push(textLine);
|
|
9273
|
+
}
|
|
9274
|
+
return brokenText;
|
|
9275
|
+
}
|
|
9276
|
+
/**
|
|
9277
|
+
* Return the font size that makes the width of a text match the given line width.
|
|
9278
|
+
* Minimum font size is 1.
|
|
9279
|
+
*
|
|
9280
|
+
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
9281
|
+
*/
|
|
9282
|
+
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
9283
|
+
let minFontSize = 1;
|
|
9284
|
+
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
9285
|
+
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
9286
|
+
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
9287
|
+
let currentTextWidth = getTextWidth(fontSize);
|
|
9288
|
+
let iterations = 0;
|
|
9289
|
+
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
9290
|
+
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
9291
|
+
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
9292
|
+
fontSize = (minFontSize + maxFontSize) / 2;
|
|
9293
|
+
currentTextWidth = getTextWidth(fontSize);
|
|
9294
|
+
iterations++;
|
|
9295
|
+
}
|
|
9296
|
+
return fontSize;
|
|
9297
|
+
}
|
|
9298
|
+
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
9299
|
+
function toTrimmedLowerCase(str) {
|
|
9300
|
+
return str ? str.toLowerCase().trim() : "";
|
|
9301
|
+
}
|
|
9302
|
+
/**
|
|
9303
|
+
* Extract the fontSize from a context font string
|
|
9304
|
+
* @param font The (context) font string to parse
|
|
9305
|
+
* @returns The fontSize in pixels
|
|
9306
|
+
*/
|
|
9307
|
+
const pxRegex = /([0-9\.]*)px/;
|
|
9308
|
+
function getContextFontSize(font) {
|
|
9309
|
+
return Number(font.match(pxRegex)?.[1]);
|
|
9310
|
+
}
|
|
9311
|
+
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
9312
|
+
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9313
|
+
if (width <= maxWidth) return text;
|
|
9314
|
+
const ellipsis = "…";
|
|
9315
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
9316
|
+
if (width <= ellipsisWidth) return text;
|
|
9317
|
+
let len = text.length;
|
|
9318
|
+
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
9319
|
+
text = text.substring(0, len);
|
|
9320
|
+
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9321
|
+
}
|
|
9322
|
+
return text + ellipsis;
|
|
9323
|
+
}
|
|
9324
|
+
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
|
|
9325
|
+
if (highlightText) {
|
|
9326
|
+
context.save();
|
|
9327
|
+
context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
|
|
9328
|
+
}
|
|
9329
|
+
context.fillText(text, position.x, position.y);
|
|
9330
|
+
if (!underline && !strikethrough) return;
|
|
9331
|
+
const measure = context.measureText(text);
|
|
9332
|
+
const textWidth = measure.width;
|
|
9333
|
+
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
9334
|
+
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9335
|
+
let { x, y } = position;
|
|
9336
|
+
let strikeY = y, underlineY = y;
|
|
9337
|
+
switch (context.textAlign) {
|
|
9338
|
+
case "center":
|
|
9339
|
+
x -= textWidth / 2;
|
|
9340
|
+
break;
|
|
9341
|
+
case "right":
|
|
9342
|
+
x -= textWidth;
|
|
9343
|
+
break;
|
|
9344
|
+
}
|
|
9345
|
+
switch (context.textBaseline) {
|
|
9346
|
+
case "top":
|
|
9347
|
+
underlineY += boxHeight - 2 * strokeWidth;
|
|
9348
|
+
strikeY += boxHeight / 2 - strokeWidth;
|
|
9349
|
+
break;
|
|
9350
|
+
case "middle":
|
|
9351
|
+
underlineY += boxHeight / 2 - strokeWidth;
|
|
9352
|
+
break;
|
|
9353
|
+
case "alphabetic":
|
|
9354
|
+
underlineY += 2 * strokeWidth;
|
|
9355
|
+
strikeY -= 3 * strokeWidth;
|
|
9356
|
+
break;
|
|
9357
|
+
case "bottom":
|
|
9358
|
+
underlineY = y;
|
|
9359
|
+
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
9360
|
+
break;
|
|
9361
|
+
}
|
|
9362
|
+
if (underline) {
|
|
9363
|
+
context.lineWidth = strokeWidth;
|
|
9364
|
+
context.strokeStyle = context.fillStyle;
|
|
9365
|
+
context.beginPath();
|
|
9366
|
+
context.moveTo(x, underlineY);
|
|
9367
|
+
context.lineTo(x + textWidth, underlineY);
|
|
9368
|
+
context.stroke();
|
|
9536
9369
|
}
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9370
|
+
if (strikethrough) {
|
|
9371
|
+
context.lineWidth = strokeWidth;
|
|
9372
|
+
context.strokeStyle = context.fillStyle;
|
|
9373
|
+
context.beginPath();
|
|
9374
|
+
context.moveTo(x, strikeY);
|
|
9375
|
+
context.lineTo(x + textWidth, strikeY);
|
|
9376
|
+
context.stroke();
|
|
9540
9377
|
}
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
"#73c476",
|
|
9561
|
-
"#00441b"
|
|
9562
|
-
],
|
|
9563
|
-
oranges: [
|
|
9564
|
-
"#fff5eb",
|
|
9565
|
-
"#fd8c3b",
|
|
9566
|
-
"#7f2704"
|
|
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);
|
|
9378
|
+
if (highlightText) context.restore();
|
|
9379
|
+
}
|
|
9380
|
+
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
9381
|
+
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
9382
|
+
const ellipsis = "...";
|
|
9383
|
+
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
9384
|
+
if (ellipsisWidth >= width) return "";
|
|
9385
|
+
let lowerBoundLen = 1;
|
|
9386
|
+
let upperBoundLen = text.length;
|
|
9387
|
+
let currentWidth;
|
|
9388
|
+
while (lowerBoundLen <= upperBoundLen) {
|
|
9389
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
9390
|
+
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
9391
|
+
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
9392
|
+
else lowerBoundLen = currentLen + 1;
|
|
9393
|
+
}
|
|
9394
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
9395
|
+
return slicedText ? slicedText + ellipsis : "";
|
|
9396
|
+
}
|
|
9590
9397
|
/**
|
|
9591
|
-
*
|
|
9592
|
-
*
|
|
9398
|
+
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
9399
|
+
* with to original's text vertical and horizontal alignment.
|
|
9593
9400
|
*/
|
|
9594
|
-
function
|
|
9595
|
-
if (
|
|
9596
|
-
|
|
9597
|
-
const
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9401
|
+
function computeRotationPosition(rect, style) {
|
|
9402
|
+
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
9403
|
+
let { x, y } = rect;
|
|
9404
|
+
const cos = Math.cos(-style.rotation);
|
|
9405
|
+
const sin = Math.sin(-style.rotation);
|
|
9406
|
+
const width = rect.textWidth - 2 * 4;
|
|
9407
|
+
const height = rect.textHeight;
|
|
9408
|
+
const center = style.align === "center";
|
|
9409
|
+
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
9410
|
+
const sh = sin * height;
|
|
9411
|
+
const sw = Math.abs(sin * width);
|
|
9412
|
+
const ch = cos * height;
|
|
9413
|
+
if (style.verticalAlign === "top") if (center) {
|
|
9414
|
+
y += sw / 2;
|
|
9415
|
+
x -= sh / 2;
|
|
9416
|
+
} else if (rotateTowardCellCenter) x -= sh;
|
|
9417
|
+
else y += sw;
|
|
9418
|
+
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
9419
|
+
y += height - ch;
|
|
9420
|
+
if (center) {
|
|
9421
|
+
y -= sw / 2;
|
|
9422
|
+
x -= sh / 2;
|
|
9423
|
+
} else if (rotateTowardCellCenter) {
|
|
9424
|
+
x -= sh;
|
|
9425
|
+
y -= sw;
|
|
9426
|
+
}
|
|
9427
|
+
} else if (center) {
|
|
9428
|
+
x -= sh / 2;
|
|
9429
|
+
y -= height / 2;
|
|
9430
|
+
if (rotateTowardCellCenter) y += sh;
|
|
9431
|
+
else y -= sh;
|
|
9432
|
+
} else if (rotateTowardCellCenter) {
|
|
9433
|
+
x -= sh;
|
|
9434
|
+
y -= sw / 2;
|
|
9435
|
+
} else y += sw / 2 + ch / 4;
|
|
9436
|
+
return {
|
|
9437
|
+
x: cos * x - sin * y,
|
|
9438
|
+
y: cos * y + sin * x
|
|
9439
|
+
};
|
|
9440
|
+
}
|
|
9441
|
+
|
|
9442
|
+
//#endregion
|
|
9443
|
+
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
9444
|
+
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
9445
|
+
const chartColorScalePlugin = {
|
|
9446
|
+
id: "chartColorScalePlugin",
|
|
9447
|
+
afterDatasetsDraw(chart, args, options) {
|
|
9448
|
+
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
9449
|
+
const ctx = chart.ctx;
|
|
9450
|
+
ctx.save();
|
|
9451
|
+
ctx.textAlign = "center";
|
|
9452
|
+
ctx.textBaseline = "middle";
|
|
9453
|
+
ctx.miterLimit = 1;
|
|
9454
|
+
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
9455
|
+
const gradientWidth = 10;
|
|
9456
|
+
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
9457
|
+
const gradientY = chart.chartArea.top;
|
|
9458
|
+
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
9459
|
+
const step = 1 / (options.colorScale.length - 1);
|
|
9460
|
+
options.colorScale.forEach((color, index) => {
|
|
9461
|
+
gradient.addColorStop(index * step, color);
|
|
9611
9462
|
});
|
|
9463
|
+
ctx.fillStyle = gradient;
|
|
9464
|
+
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
9465
|
+
ctx.fillStyle = options.fontColor ?? "black";
|
|
9466
|
+
ctx.font = getDefaultContextFont(12);
|
|
9467
|
+
ctx.textAlign = "left";
|
|
9468
|
+
let minValue = Math.round(options.minValue * 100) / 100;
|
|
9469
|
+
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
9470
|
+
if (options.minValue === options.maxValue) {
|
|
9471
|
+
minValue -= 1;
|
|
9472
|
+
maxValue += 1;
|
|
9473
|
+
}
|
|
9474
|
+
const formattedMaxValue = humanizeNumber({
|
|
9475
|
+
value: maxValue,
|
|
9476
|
+
format: void 0
|
|
9477
|
+
}, options.locale);
|
|
9478
|
+
const formattedMinValue = humanizeNumber({
|
|
9479
|
+
value: minValue,
|
|
9480
|
+
format: void 0
|
|
9481
|
+
}, options.locale);
|
|
9482
|
+
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
9483
|
+
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
9484
|
+
ctx.restore();
|
|
9612
9485
|
}
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9486
|
+
};
|
|
9487
|
+
|
|
9488
|
+
//#endregion
|
|
9489
|
+
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
9490
|
+
function getFunnelChartController() {
|
|
9491
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9492
|
+
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
9493
|
+
static id = "funnel";
|
|
9494
|
+
static defaults = {
|
|
9495
|
+
...globalThis.Chart?.BarController.defaults,
|
|
9496
|
+
dataElementType: "funnel",
|
|
9497
|
+
animation: { duration: (ctx) => {
|
|
9498
|
+
if (ctx.type !== "data") return 1e3;
|
|
9499
|
+
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
9500
|
+
} }
|
|
9501
|
+
};
|
|
9502
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9503
|
+
updateElements(rects, start, count, mode) {
|
|
9504
|
+
super.updateElements(rects, start, count, mode);
|
|
9505
|
+
for (let i = start; i < start + count; i++) {
|
|
9506
|
+
const rect = rects[i];
|
|
9507
|
+
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
9508
|
+
}
|
|
9509
|
+
}
|
|
9617
9510
|
};
|
|
9618
9511
|
}
|
|
9619
|
-
function
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9512
|
+
function getFunnelChartElement() {
|
|
9513
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9514
|
+
/**
|
|
9515
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9516
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9517
|
+
*/
|
|
9518
|
+
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
9519
|
+
static id = "funnel";
|
|
9520
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9521
|
+
draw(ctx) {
|
|
9522
|
+
ctx.save();
|
|
9523
|
+
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
9524
|
+
"x",
|
|
9525
|
+
"y",
|
|
9526
|
+
"width",
|
|
9527
|
+
"height",
|
|
9528
|
+
"nextElement",
|
|
9529
|
+
"base",
|
|
9530
|
+
"options"
|
|
9531
|
+
]);
|
|
9532
|
+
const width = getElementWidth(this);
|
|
9533
|
+
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
9534
|
+
const startX = Math.min(x, base);
|
|
9535
|
+
const startY = y - height / 2;
|
|
9536
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9537
|
+
ctx.beginPath();
|
|
9538
|
+
ctx.moveTo(startX, startY);
|
|
9539
|
+
ctx.lineTo(startX + width, startY);
|
|
9540
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9541
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9542
|
+
ctx.closePath();
|
|
9543
|
+
ctx.fill();
|
|
9544
|
+
if (options.borderWidth) {
|
|
9545
|
+
ctx.strokeStyle = options.borderColor;
|
|
9546
|
+
ctx.lineWidth = options.borderWidth;
|
|
9547
|
+
ctx.stroke();
|
|
9548
|
+
}
|
|
9549
|
+
ctx.restore();
|
|
9550
|
+
}
|
|
9551
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9552
|
+
inRange(mouseX, mouseY) {
|
|
9553
|
+
const { x, y, height, nextElement, base } = this.getProps([
|
|
9554
|
+
"x",
|
|
9555
|
+
"y",
|
|
9556
|
+
"width",
|
|
9557
|
+
"height",
|
|
9558
|
+
"nextElement",
|
|
9559
|
+
"base",
|
|
9560
|
+
"options"
|
|
9561
|
+
]);
|
|
9562
|
+
const width = getElementWidth(this);
|
|
9563
|
+
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
9564
|
+
const startX = Math.min(x, base);
|
|
9565
|
+
const startY = y - height / 2;
|
|
9566
|
+
if (mouseY < startY || mouseY > startY + height) return false;
|
|
9567
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9568
|
+
const left = startX + offset * (mouseY - startY) / height;
|
|
9569
|
+
const right = startX + width - offset * (mouseY - startY) / height;
|
|
9570
|
+
if (mouseX < left || mouseX > right) return false;
|
|
9571
|
+
return true;
|
|
9572
|
+
}
|
|
9573
|
+
};
|
|
9629
9574
|
}
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9575
|
+
/**
|
|
9576
|
+
* Get an element width.
|
|
9577
|
+
*
|
|
9578
|
+
* The property width is undefined during animations, we need to compute it manually.
|
|
9579
|
+
*/
|
|
9580
|
+
function getElementWidth(element) {
|
|
9581
|
+
const { x, base } = element.getProps(["x", "base"]);
|
|
9582
|
+
return Math.max(x, base) - Math.min(x, base);
|
|
9636
9583
|
}
|
|
9584
|
+
/**
|
|
9585
|
+
* Position the tooltip inside the trapezoid.
|
|
9586
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9587
|
+
*/
|
|
9588
|
+
const funnelTooltipPositioner = function(elements) {
|
|
9589
|
+
if (!elements.length) return {
|
|
9590
|
+
x: 0,
|
|
9591
|
+
y: 0
|
|
9592
|
+
};
|
|
9593
|
+
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
9594
|
+
"x",
|
|
9595
|
+
"y",
|
|
9596
|
+
"width",
|
|
9597
|
+
"height",
|
|
9598
|
+
"base"
|
|
9599
|
+
]);
|
|
9600
|
+
const startX = Math.min(x, base);
|
|
9601
|
+
const startY = y - height / 2;
|
|
9602
|
+
return {
|
|
9603
|
+
x: startX + width * 2 / 3,
|
|
9604
|
+
y: startY + height / 2
|
|
9605
|
+
};
|
|
9606
|
+
};
|
|
9607
|
+
|
|
9608
|
+
//#endregion
|
|
9609
|
+
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
9610
|
+
const chartMinorGridPlugin = {
|
|
9611
|
+
id: "o-spreadsheet-minor-gridlines",
|
|
9612
|
+
beforeDatasetsDraw(chart) {
|
|
9613
|
+
const ctx = chart.ctx;
|
|
9614
|
+
const chartArea = chart.chartArea;
|
|
9615
|
+
if (!chartArea) return;
|
|
9616
|
+
for (const scaleId in chart.scales) {
|
|
9617
|
+
const scale = chart.scales[scaleId];
|
|
9618
|
+
const options = scale.options;
|
|
9619
|
+
const minor = options?.grid?.minor;
|
|
9620
|
+
if (!minor?.display) continue;
|
|
9621
|
+
const showMajorGrid = options?.grid?.display;
|
|
9622
|
+
const ticks = scale.ticks;
|
|
9623
|
+
if (!ticks || ticks.length < 2) continue;
|
|
9624
|
+
ctx.save();
|
|
9625
|
+
ctx.lineWidth = 1;
|
|
9626
|
+
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
9627
|
+
for (let i = 0; i < ticks.length - 1; i++) {
|
|
9628
|
+
const start = scale.getPixelForTick(i);
|
|
9629
|
+
const end = scale.getPixelForTick(i + 1);
|
|
9630
|
+
if (!isFinite(start) || !isFinite(end)) continue;
|
|
9631
|
+
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
9632
|
+
const ratio = j / 4;
|
|
9633
|
+
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
9634
|
+
ctx.beginPath();
|
|
9635
|
+
if (scale.isHorizontal()) {
|
|
9636
|
+
ctx.moveTo(position, chartArea.top);
|
|
9637
|
+
ctx.lineTo(position, chartArea.bottom);
|
|
9638
|
+
} else {
|
|
9639
|
+
ctx.moveTo(chartArea.left, position);
|
|
9640
|
+
ctx.lineTo(chartArea.right, position);
|
|
9641
|
+
}
|
|
9642
|
+
ctx.stroke();
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
ctx.restore();
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
};
|
|
9637
9649
|
|
|
9638
9650
|
//#endregion
|
|
9639
9651
|
//#region src/xlsx/constants.ts
|
|
@@ -10127,6 +10139,7 @@ function getChartBackgroundColor({ background }, getters) {
|
|
|
10127
10139
|
//#endregion
|
|
10128
10140
|
//#region src/components/figures/chart/chartJs/chartjs_show_values_plugin.ts
|
|
10129
10141
|
const MINIMAL_VERTICAL_DISTANCE = 13;
|
|
10142
|
+
const HORIZONTAL_PADDING = 3;
|
|
10130
10143
|
function isLineOverlayOnBarChart(options, dataset) {
|
|
10131
10144
|
return options.type === "bar" && dataset.type === "line";
|
|
10132
10145
|
}
|
|
@@ -10136,175 +10149,276 @@ const chartShowValuesPlugin = {
|
|
|
10136
10149
|
afterDatasetsDraw(chart, args, options) {
|
|
10137
10150
|
if (!options.showValues) return;
|
|
10138
10151
|
if (!chart._metasets?.[0]?.data) return;
|
|
10139
|
-
const ctx = chart.ctx;
|
|
10140
|
-
ctx.save();
|
|
10141
|
-
ctx.textAlign = "center";
|
|
10142
|
-
ctx.textBaseline = "middle";
|
|
10143
|
-
ctx.miterLimit = 1;
|
|
10144
10152
|
switch (options.type) {
|
|
10145
10153
|
case "pie":
|
|
10146
|
-
|
|
10154
|
+
drawPieValues(chart, options);
|
|
10147
10155
|
break;
|
|
10148
10156
|
case "line":
|
|
10149
10157
|
case "scatter":
|
|
10158
|
+
drawLineValues(chart, options);
|
|
10159
|
+
break;
|
|
10150
10160
|
case "combo":
|
|
10151
10161
|
case "waterfall":
|
|
10162
|
+
drawComboValues(chart, options);
|
|
10163
|
+
break;
|
|
10152
10164
|
case "radar":
|
|
10153
|
-
|
|
10165
|
+
drawRadarValues(chart, options);
|
|
10154
10166
|
break;
|
|
10155
10167
|
case "bar":
|
|
10156
|
-
options.horizontal ?
|
|
10168
|
+
options.horizontal ? drawHorizontalBarValues(chart, options) : drawVerticalBarValues(chart, options);
|
|
10157
10169
|
break;
|
|
10158
10170
|
case "pyramid":
|
|
10159
|
-
|
|
10171
|
+
drawHorizontalBarValues(chart, options);
|
|
10160
10172
|
break;
|
|
10161
10173
|
case "calendar":
|
|
10162
|
-
|
|
10174
|
+
drawCalendarValues(chart, options);
|
|
10163
10175
|
break;
|
|
10164
10176
|
case "bubble":
|
|
10165
|
-
|
|
10177
|
+
drawBubbleValues(chart, options);
|
|
10166
10178
|
break;
|
|
10167
10179
|
case "funnel":
|
|
10168
|
-
|
|
10180
|
+
drawFunnelValues(chart, options);
|
|
10169
10181
|
break;
|
|
10170
10182
|
}
|
|
10171
|
-
ctx.restore();
|
|
10172
10183
|
}
|
|
10173
10184
|
};
|
|
10174
|
-
function
|
|
10175
|
-
|
|
10176
|
-
ctx
|
|
10177
|
-
ctx.
|
|
10178
|
-
ctx.
|
|
10179
|
-
|
|
10180
|
-
|
|
10185
|
+
function drawValues(args) {
|
|
10186
|
+
const { chart, options, getNumberValue, direction } = args;
|
|
10187
|
+
const ctx = chart.ctx;
|
|
10188
|
+
ctx.save();
|
|
10189
|
+
ctx.textAlign = "center";
|
|
10190
|
+
ctx.textBaseline = "middle";
|
|
10191
|
+
ctx.miterLimit = 1;
|
|
10181
10192
|
const textsPositions = {};
|
|
10182
10193
|
for (const dataset of chart._metasets) {
|
|
10183
|
-
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden
|
|
10184
|
-
const yAxisScale = chart.scales[dataset.yAxisID];
|
|
10194
|
+
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
|
|
10185
10195
|
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
10186
|
-
const
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
const
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10196
|
+
const numberValue = getNumberValue(dataset, i);
|
|
10197
|
+
if (numberValue === void 0 || isNaN(numberValue)) continue;
|
|
10198
|
+
const chartElement = dataset.data[i];
|
|
10199
|
+
const elementColor = chartElement.options.backgroundColor;
|
|
10200
|
+
if (!elementColor || colorToRGBA(elementColor).a !== 1) continue;
|
|
10201
|
+
const valueToDisplay = options.callback(numberValue, dataset, i);
|
|
10202
|
+
const textSize = getTextDimensions(valueToDisplay, ctx);
|
|
10203
|
+
const callbackArgs = {
|
|
10204
|
+
dataset,
|
|
10205
|
+
chartElement,
|
|
10206
|
+
numberValue,
|
|
10207
|
+
valueIndex: i,
|
|
10208
|
+
textSize,
|
|
10209
|
+
options,
|
|
10210
|
+
chart
|
|
10211
|
+
};
|
|
10212
|
+
const position = args.getValuePosition(callbackArgs);
|
|
10213
|
+
if (args.shouldSkipValue?.(callbackArgs)) continue;
|
|
10214
|
+
if (direction === "vertical") {
|
|
10215
|
+
const key = Math.round(position.x);
|
|
10216
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10217
|
+
for (const otherPosition of textsPositions[key] || []) if (Math.abs(otherPosition - position.y) < MINIMAL_VERTICAL_DISTANCE) position.y = otherPosition + MINIMAL_VERTICAL_DISTANCE * (numberValue < 0 ? 1 : -1);
|
|
10218
|
+
textsPositions[key].push(position.y);
|
|
10219
|
+
} else {
|
|
10220
|
+
const key = Math.round(position.y);
|
|
10221
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10222
|
+
for (const otherPosition of textsPositions[key]) if (Math.abs(otherPosition - position.x) < textSize.width) position.x = numberValue < 0 ? otherPosition - textSize.width - HORIZONTAL_PADDING : otherPosition + textSize.width + HORIZONTAL_PADDING;
|
|
10223
|
+
textsPositions[key].push(position.x);
|
|
10224
|
+
}
|
|
10225
|
+
const { strokeColor, textColor } = args.getTextColors(callbackArgs);
|
|
10226
|
+
if (!!strokeColor) {
|
|
10227
|
+
ctx.strokeStyle = strokeColor;
|
|
10228
|
+
ctx.lineWidth = 3;
|
|
10229
|
+
ctx.strokeText(valueToDisplay, position.x, position.y);
|
|
10230
|
+
}
|
|
10231
|
+
ctx.fillStyle = textColor;
|
|
10232
|
+
ctx.lineWidth = 1;
|
|
10233
|
+
ctx.fillText(valueToDisplay, position.x, position.y);
|
|
10207
10234
|
}
|
|
10208
10235
|
}
|
|
10236
|
+
ctx.restore();
|
|
10209
10237
|
}
|
|
10210
|
-
function
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
}
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10238
|
+
function drawVerticalBarValues(chart, options) {
|
|
10239
|
+
drawValues({
|
|
10240
|
+
chart,
|
|
10241
|
+
options,
|
|
10242
|
+
direction: "vertical",
|
|
10243
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10244
|
+
getValuePosition: getVerticalBarValuePosition,
|
|
10245
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10246
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10247
|
+
});
|
|
10248
|
+
}
|
|
10249
|
+
function drawRadarValues(chart, options) {
|
|
10250
|
+
drawValues({
|
|
10251
|
+
chart,
|
|
10252
|
+
options,
|
|
10253
|
+
direction: "vertical",
|
|
10254
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].r),
|
|
10255
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10256
|
+
x: chartElement.x,
|
|
10257
|
+
y: chartElement.y - 10
|
|
10258
|
+
}),
|
|
10259
|
+
shouldSkipValue: () => false,
|
|
10260
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10261
|
+
});
|
|
10262
|
+
}
|
|
10263
|
+
function drawLineValues(chart, options) {
|
|
10264
|
+
drawValues({
|
|
10265
|
+
chart,
|
|
10266
|
+
options,
|
|
10267
|
+
direction: "vertical",
|
|
10268
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10269
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10270
|
+
x: chartElement.x,
|
|
10271
|
+
y: chartElement.y - 10
|
|
10272
|
+
}),
|
|
10273
|
+
shouldSkipValue: () => false,
|
|
10274
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10275
|
+
});
|
|
10276
|
+
}
|
|
10277
|
+
function drawComboValues(chart, options) {
|
|
10278
|
+
drawValues({
|
|
10279
|
+
chart,
|
|
10280
|
+
options,
|
|
10281
|
+
direction: "vertical",
|
|
10282
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10283
|
+
getValuePosition: (args) => args.dataset.type === "line" ? {
|
|
10284
|
+
x: args.chartElement.x,
|
|
10285
|
+
y: args.chartElement.y - 10
|
|
10286
|
+
} : getVerticalBarValuePosition(args),
|
|
10287
|
+
shouldSkipValue: () => false,
|
|
10288
|
+
getTextColors: (args) => args.dataset.type === "line" ? chartElementColoredTextWithChartBackgroundHalo(args) : chartBackgroundColoredTextWithElementColoredHalo(args)
|
|
10289
|
+
});
|
|
10290
|
+
}
|
|
10291
|
+
function drawCalendarValues(chart, options) {
|
|
10292
|
+
drawValues({
|
|
10293
|
+
chart,
|
|
10294
|
+
options,
|
|
10295
|
+
direction: "vertical",
|
|
10296
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10297
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10298
|
+
x: chartElement.x,
|
|
10299
|
+
y: chartElement.y + chartElement.height / 2
|
|
10300
|
+
}),
|
|
10301
|
+
shouldSkipValue: ({ chartElement, textSize }) => textSize.height + 2 > Math.abs(chartElement.height) - 2,
|
|
10302
|
+
getTextColors: ({ chartElement }) => ({
|
|
10303
|
+
strokeColor: void 0,
|
|
10304
|
+
textColor: chartFontColor(chartElement.options.backgroundColor)
|
|
10305
|
+
})
|
|
10306
|
+
});
|
|
10307
|
+
}
|
|
10308
|
+
function drawBubbleValues(chart, options) {
|
|
10309
|
+
const canDrawTextInsideBubble = (chartElement, textSize) => {
|
|
10310
|
+
const radius = chartElement.options.radius ?? globalThis.Chart?.defaults.elements.point.radius ?? 3;
|
|
10311
|
+
return textSize.height < radius * 2;
|
|
10312
|
+
};
|
|
10313
|
+
drawValues({
|
|
10314
|
+
chart,
|
|
10315
|
+
options,
|
|
10316
|
+
direction: "vertical",
|
|
10317
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10318
|
+
getValuePosition: ({ chartElement, textSize }) => ({
|
|
10319
|
+
x: chartElement.x,
|
|
10320
|
+
y: canDrawTextInsideBubble(chartElement, textSize) ? chartElement.y : chartElement.y - 10
|
|
10321
|
+
}),
|
|
10322
|
+
shouldSkipValue: () => false,
|
|
10323
|
+
getTextColors: (args) => {
|
|
10324
|
+
return canDrawTextInsideBubble(args.chartElement, args.textSize) ? chartBackgroundColoredTextWithElementColoredHalo(args) : chartElementColoredTextWithChartBackgroundHalo(args);
|
|
10325
|
+
}
|
|
10326
|
+
});
|
|
10327
|
+
}
|
|
10328
|
+
function drawHorizontalBarValues(chart, options) {
|
|
10329
|
+
drawValues({
|
|
10330
|
+
chart,
|
|
10331
|
+
options,
|
|
10332
|
+
direction: "horizontal",
|
|
10333
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10334
|
+
getValuePosition: ({ chartElement, numberValue, textSize, dataset }) => {
|
|
10335
|
+
const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
|
|
10336
|
+
const distanceFromAxisOrigin = Math.abs(chartElement.x - xZeroLine);
|
|
10273
10337
|
let xPosition;
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
}
|
|
10283
|
-
|
|
10284
|
-
}
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
}
|
|
10338
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10339
|
+
if (distanceFromAxisOrigin < textSize.width) xPosition = xZeroLine + sign * (textSize.width / 2 + HORIZONTAL_PADDING);
|
|
10340
|
+
else xPosition = chartElement.x - sign * (chartElement.width / 2);
|
|
10341
|
+
return {
|
|
10342
|
+
x: xPosition,
|
|
10343
|
+
y: chartElement.y
|
|
10344
|
+
};
|
|
10345
|
+
},
|
|
10346
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10347
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10348
|
+
});
|
|
10349
|
+
}
|
|
10350
|
+
function drawFunnelValues(chart, options) {
|
|
10351
|
+
drawValues({
|
|
10352
|
+
chart,
|
|
10353
|
+
options,
|
|
10354
|
+
direction: "horizontal",
|
|
10355
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10356
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10357
|
+
x: chartElement.x - chartElement.width / 2,
|
|
10358
|
+
y: chartElement.y
|
|
10359
|
+
}),
|
|
10360
|
+
shouldSkipValue: () => false,
|
|
10361
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10362
|
+
});
|
|
10363
|
+
}
|
|
10364
|
+
function drawPieValues(chart, options) {
|
|
10365
|
+
const getSliceDimensions = (chartElement) => {
|
|
10366
|
+
const { startAngle, endAngle, innerRadius, outerRadius } = chartElement;
|
|
10367
|
+
return {
|
|
10368
|
+
midAngle: (startAngle + endAngle) / 2,
|
|
10369
|
+
midRadius: (innerRadius + outerRadius) / 2
|
|
10370
|
+
};
|
|
10371
|
+
};
|
|
10372
|
+
drawValues({
|
|
10373
|
+
chart,
|
|
10374
|
+
options,
|
|
10375
|
+
direction: "vertical",
|
|
10376
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i]),
|
|
10377
|
+
getValuePosition: ({ chartElement }) => {
|
|
10378
|
+
const { midAngle, midRadius } = getSliceDimensions(chartElement);
|
|
10379
|
+
return {
|
|
10380
|
+
x: chartElement.x + midRadius * Math.cos(midAngle),
|
|
10381
|
+
y: chartElement.y + midRadius * Math.sin(midAngle)
|
|
10382
|
+
};
|
|
10383
|
+
},
|
|
10384
|
+
shouldSkipValue: ({ chartElement, textSize }) => {
|
|
10385
|
+
const { midRadius } = getSliceDimensions(chartElement);
|
|
10386
|
+
const sliceAngle = chartElement.endAngle - chartElement.startAngle;
|
|
10387
|
+
const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
|
|
10388
|
+
return sliceAngle < Math.PI / 2 && (textSize.width >= midWidth || midWidth < textSize.height);
|
|
10389
|
+
},
|
|
10390
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10391
|
+
});
|
|
10392
|
+
}
|
|
10393
|
+
function getTextDimensions(text, ctx) {
|
|
10394
|
+
return computeCachedTextDimension(ctx, text, computeTextFont({ fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px"));
|
|
10395
|
+
}
|
|
10396
|
+
function chartBackgroundColoredTextWithElementColoredHalo(args) {
|
|
10397
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10398
|
+
return {
|
|
10399
|
+
strokeColor: chartElement.options.backgroundColor,
|
|
10400
|
+
textColor: options.background(numberValue, dataset, valueIndex) || "#ffffff"
|
|
10401
|
+
};
|
|
10402
|
+
}
|
|
10403
|
+
function chartElementColoredTextWithChartBackgroundHalo(args) {
|
|
10404
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10405
|
+
return {
|
|
10406
|
+
strokeColor: options.background(numberValue, dataset, valueIndex) || "#ffffff",
|
|
10407
|
+
textColor: chartElement.options.backgroundColor
|
|
10408
|
+
};
|
|
10409
|
+
}
|
|
10410
|
+
function getVerticalBarValuePosition(args) {
|
|
10411
|
+
const { chartElement, numberValue, dataset, textSize, chart } = args;
|
|
10412
|
+
const yZeroLine = chart.scales[dataset.yAxisID].getPixelForValue(0);
|
|
10413
|
+
const distanceFromAxisOrigin = Math.abs(yZeroLine - chartElement.y);
|
|
10414
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10415
|
+
let yPosition = 0;
|
|
10416
|
+
if (distanceFromAxisOrigin < textSize.height) yPosition = yZeroLine - sign * (textSize.height / 2);
|
|
10417
|
+
else yPosition = chartElement.y + sign * (chartElement.height / 2);
|
|
10418
|
+
return {
|
|
10419
|
+
x: chartElement.x,
|
|
10420
|
+
y: yPosition
|
|
10421
|
+
};
|
|
10308
10422
|
}
|
|
10309
10423
|
|
|
10310
10424
|
//#endregion
|
|
@@ -11161,6 +11275,7 @@ function getRadarChartDatasets(definition, args) {
|
|
|
11161
11275
|
hidden,
|
|
11162
11276
|
borderColor,
|
|
11163
11277
|
backgroundColor: borderColor,
|
|
11278
|
+
pointBackgroundColor: borderColor,
|
|
11164
11279
|
pointRadius: definition.hideDataMarkers ? 0 : 3
|
|
11165
11280
|
};
|
|
11166
11281
|
if (fill) {
|
|
@@ -12073,11 +12188,11 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12073
12188
|
if (structure.baseline) {
|
|
12074
12189
|
ctx.font = structure.baseline.style.font;
|
|
12075
12190
|
ctx.fillStyle = structure.baseline.style.color;
|
|
12076
|
-
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
|
|
12191
|
+
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
|
|
12077
12192
|
}
|
|
12078
12193
|
if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
|
|
12079
12194
|
ctx.save();
|
|
12080
|
-
ctx.fillStyle = structure.baselineArrow.style.color;
|
|
12195
|
+
ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
|
|
12081
12196
|
ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
|
|
12082
12197
|
const ratio = structure.baselineArrow.style.size / 10;
|
|
12083
12198
|
ctx.scale(ratio, ratio);
|
|
@@ -12095,18 +12210,18 @@ function drawScoreChart(structure, canvas, zoom = 1) {
|
|
|
12095
12210
|
const descr = structure.baselineDescr;
|
|
12096
12211
|
ctx.font = descr.style.font;
|
|
12097
12212
|
ctx.fillStyle = descr.style.color;
|
|
12098
|
-
ctx
|
|
12213
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
|
|
12099
12214
|
}
|
|
12100
12215
|
if (structure.key) {
|
|
12101
12216
|
ctx.font = structure.key.style.font;
|
|
12102
12217
|
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);
|
|
12218
|
+
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
12219
|
}
|
|
12105
12220
|
if (structure.keyDescr) {
|
|
12106
12221
|
const descr = structure.keyDescr;
|
|
12107
12222
|
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12108
12223
|
ctx.fillStyle = descr.style.color;
|
|
12109
|
-
ctx
|
|
12224
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
|
|
12110
12225
|
}
|
|
12111
12226
|
if (structure.progressBar) {
|
|
12112
12227
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
@@ -12330,29 +12445,34 @@ var ScorecardChartConfigBuilder = class {
|
|
|
12330
12445
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12331
12446
|
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12332
12447
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12333
|
-
underline: this.runtime.keyValueStyle?.underline
|
|
12448
|
+
underline: this.runtime.keyValueStyle?.underline,
|
|
12449
|
+
highlightText: this.runtime.keyHighlight
|
|
12334
12450
|
},
|
|
12335
12451
|
keyDescr: {
|
|
12336
12452
|
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12337
12453
|
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12338
12454
|
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12339
|
-
underline: this.runtime.keyValueDescrStyle?.underline
|
|
12455
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12456
|
+
highlightText: this.runtime.keyHighlight
|
|
12340
12457
|
},
|
|
12341
12458
|
baselineValue: {
|
|
12342
12459
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12343
12460
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12344
12461
|
underline: this.runtime.baselineStyle?.underline,
|
|
12345
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12462
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12463
|
+
highlightText: this.runtime.baselineHighlight
|
|
12346
12464
|
},
|
|
12347
12465
|
baselineDescr: {
|
|
12348
12466
|
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12349
12467
|
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12350
12468
|
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12351
|
-
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
|
|
12469
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12470
|
+
highlightText: this.runtime.baselineHighlight
|
|
12352
12471
|
},
|
|
12353
12472
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
|
|
12354
12473
|
size: this.keyValue ? .8 * baselineValueFontSize : 0,
|
|
12355
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12474
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12475
|
+
highlight: this.runtime.baselineHighlight
|
|
12356
12476
|
}
|
|
12357
12477
|
};
|
|
12358
12478
|
}
|
|
@@ -12448,11 +12568,14 @@ var ScorecardChart = class extends Component {
|
|
|
12448
12568
|
});
|
|
12449
12569
|
(0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
|
|
12450
12570
|
}
|
|
12571
|
+
config(canvasRect, zoom) {
|
|
12572
|
+
return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
|
|
12573
|
+
}
|
|
12451
12574
|
createChart() {
|
|
12452
12575
|
const canvas = this.canvas();
|
|
12453
12576
|
if (!canvas) return;
|
|
12454
12577
|
const zoom = this.env.model.getters.getViewportZoomLevel();
|
|
12455
|
-
drawScoreChart(
|
|
12578
|
+
drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
|
|
12456
12579
|
}
|
|
12457
12580
|
};
|
|
12458
12581
|
|
|
@@ -28056,7 +28179,7 @@ function filterInvalidCalendarDataPoints(labels, datasets) {
|
|
|
28056
28179
|
*/
|
|
28057
28180
|
function filterInvalidHierarchicalPoints(values, hierarchy) {
|
|
28058
28181
|
const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
|
|
28059
|
-
const isEmpty = (value) => value === null || value === "";
|
|
28182
|
+
const isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
28060
28183
|
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
|
|
28061
28184
|
const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
|
|
28062
28185
|
if (isEmpty(groups[0]?.value)) return false;
|
|
@@ -28620,23 +28743,11 @@ function getChartShowValues(definition, args) {
|
|
|
28620
28743
|
}
|
|
28621
28744
|
function getCalendarChartShowValues(definition, args) {
|
|
28622
28745
|
const { locale, axisFormats } = args;
|
|
28623
|
-
let background = (_value, dataset, index) => definition.background;
|
|
28624
|
-
const values = args.dataSetsValues.flat().flatMap((dsv) => dsv?.data.filter(isNumberResult)).map((cell) => cell.value);
|
|
28625
|
-
if (values.length) {
|
|
28626
|
-
const min = Math.min(...values);
|
|
28627
|
-
const max = Math.max(...values);
|
|
28628
|
-
const colorScale = getRuntimeColorScale(definition.colorScale ?? schemeToColorScale("oranges"), min, max);
|
|
28629
|
-
background = (_value, dataset, index) => {
|
|
28630
|
-
const value = dataset._dataset.values[index];
|
|
28631
|
-
if (value === void 0) return definition.background;
|
|
28632
|
-
return chartFontColor(colorScale(value));
|
|
28633
|
-
};
|
|
28634
|
-
}
|
|
28635
28746
|
return {
|
|
28636
28747
|
type: "calendar",
|
|
28637
28748
|
horizontal: false,
|
|
28638
28749
|
showValues: "showValues" in definition ? !!definition.showValues : false,
|
|
28639
|
-
background,
|
|
28750
|
+
background: () => definition.background,
|
|
28640
28751
|
callback: (_value, dataset, index) => {
|
|
28641
28752
|
const value = dataset._dataset.values[index];
|
|
28642
28753
|
return value === void 0 ? "" : humanizeNumber({
|
|
@@ -34537,6 +34648,35 @@ var CellIsRuleEditor = class extends Component {
|
|
|
34537
34648
|
}
|
|
34538
34649
|
};
|
|
34539
34650
|
|
|
34651
|
+
//#endregion
|
|
34652
|
+
//#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
|
|
34653
|
+
var CalendarButton = class extends Component {
|
|
34654
|
+
static template = "o-spreadsheet-CalendarButton";
|
|
34655
|
+
props = (0, _odoo_owl.props)({
|
|
34656
|
+
value: types.string().optional(""),
|
|
34657
|
+
onChange: types.function()
|
|
34658
|
+
});
|
|
34659
|
+
datePickerRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
34660
|
+
openCalendar() {
|
|
34661
|
+
this.datePickerRef()?.showPicker();
|
|
34662
|
+
}
|
|
34663
|
+
formatDateForInput(value) {
|
|
34664
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34665
|
+
return dateValue ? formatValue(dateValue.value, {
|
|
34666
|
+
format: "yyyy-mm-dd",
|
|
34667
|
+
locale: DEFAULT_LOCALE
|
|
34668
|
+
}) : "";
|
|
34669
|
+
}
|
|
34670
|
+
onDateInputValueChanged(value) {
|
|
34671
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34672
|
+
const formattedValue = dateValue ? formatValue(dateValue.value, {
|
|
34673
|
+
format: DEFAULT_LOCALE.dateFormat,
|
|
34674
|
+
locale: DEFAULT_LOCALE
|
|
34675
|
+
}) : "";
|
|
34676
|
+
this.props.onChange(formattedValue);
|
|
34677
|
+
}
|
|
34678
|
+
};
|
|
34679
|
+
|
|
34540
34680
|
//#endregion
|
|
34541
34681
|
//#region src/components/side_panel/criterion_form/criterion_form.ts
|
|
34542
34682
|
var CriterionForm = class extends Component {
|
|
@@ -35225,7 +35365,8 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35225
35365
|
static template = "o-spreadsheet-DataValidationDateCriterion";
|
|
35226
35366
|
static components = {
|
|
35227
35367
|
CriterionInput,
|
|
35228
|
-
Select
|
|
35368
|
+
Select,
|
|
35369
|
+
CalendarButton
|
|
35229
35370
|
};
|
|
35230
35371
|
get currentDateValue() {
|
|
35231
35372
|
return this.props.criterion.dateValue || "exactDate";
|
|
@@ -35251,7 +35392,10 @@ var DateCriterionForm = class extends CriterionForm {
|
|
|
35251
35392
|
//#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
|
|
35252
35393
|
var DoubleInputCriterionForm = class extends CriterionForm {
|
|
35253
35394
|
static template = "o-spreadsheet-DoubleInputCriterionForm";
|
|
35254
|
-
static components = {
|
|
35395
|
+
static components = {
|
|
35396
|
+
CriterionInput,
|
|
35397
|
+
CalendarButton
|
|
35398
|
+
};
|
|
35255
35399
|
onFirstValueChanged(value) {
|
|
35256
35400
|
const values = this.props.criterion.values;
|
|
35257
35401
|
this.updateCriterion({ values: [value, values[1] || ""] });
|
|
@@ -35260,6 +35404,9 @@ var DoubleInputCriterionForm = class extends CriterionForm {
|
|
|
35260
35404
|
const values = this.props.criterion.values;
|
|
35261
35405
|
this.updateCriterion({ values: [values[0] || "", value] });
|
|
35262
35406
|
}
|
|
35407
|
+
get isDateType() {
|
|
35408
|
+
return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
|
|
35409
|
+
}
|
|
35263
35410
|
};
|
|
35264
35411
|
|
|
35265
35412
|
//#endregion
|
|
@@ -46606,7 +46753,7 @@ var FiguresContainer = class extends Component {
|
|
|
46606
46753
|
horizontalSnap: void 0,
|
|
46607
46754
|
verticalSnap: void 0,
|
|
46608
46755
|
cancelDnd: void 0,
|
|
46609
|
-
|
|
46756
|
+
overlappingChartOrCarousel: void 0
|
|
46610
46757
|
});
|
|
46611
46758
|
setup() {
|
|
46612
46759
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -46622,7 +46769,7 @@ var FiguresContainer = class extends Component {
|
|
|
46622
46769
|
this.dnd.selectedRect = void 0;
|
|
46623
46770
|
this.dnd.horizontalSnap = void 0;
|
|
46624
46771
|
this.dnd.verticalSnap = void 0;
|
|
46625
|
-
this.dnd.
|
|
46772
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46626
46773
|
this.dnd.cancelDnd = void 0;
|
|
46627
46774
|
}
|
|
46628
46775
|
});
|
|
@@ -46760,11 +46907,11 @@ var FiguresContainer = class extends Component {
|
|
|
46760
46907
|
hasStartedDnd = true;
|
|
46761
46908
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
|
|
46762
46909
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
46763
|
-
let
|
|
46910
|
+
let overlappingChartOrCarousel = void 0;
|
|
46764
46911
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
46765
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart"))
|
|
46766
|
-
this.dnd.
|
|
46767
|
-
if (!
|
|
46912
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
|
|
46913
|
+
this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
|
|
46914
|
+
if (!overlappingChartOrCarousel) {
|
|
46768
46915
|
const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
|
|
46769
46916
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
46770
46917
|
this.dnd.selectedRect = this.getDndFigureRect();
|
|
@@ -46785,7 +46932,7 @@ var FiguresContainer = class extends Component {
|
|
|
46785
46932
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
46786
46933
|
return;
|
|
46787
46934
|
}
|
|
46788
|
-
if (!this.dnd.
|
|
46935
|
+
if (!this.dnd.overlappingChartOrCarousel) {
|
|
46789
46936
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
46790
46937
|
return {
|
|
46791
46938
|
sheetId,
|
|
@@ -46795,20 +46942,25 @@ var FiguresContainer = class extends Component {
|
|
|
46795
46942
|
}) || [];
|
|
46796
46943
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
46797
46944
|
} else {
|
|
46798
|
-
const
|
|
46945
|
+
const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
|
|
46799
46946
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
46800
|
-
this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46947
|
+
if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46801
46948
|
sheetId,
|
|
46802
|
-
carouselFigureId,
|
|
46949
|
+
carouselFigureId: overlappingFigureId,
|
|
46803
46950
|
chartFigureIds
|
|
46804
46951
|
});
|
|
46952
|
+
else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
46953
|
+
sheetId,
|
|
46954
|
+
baseFigureId: overlappingFigureId,
|
|
46955
|
+
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
46956
|
+
});
|
|
46805
46957
|
}
|
|
46806
46958
|
this.dnd.draggedFigure = void 0;
|
|
46807
46959
|
this.dnd.selectedFigures = void 0;
|
|
46808
46960
|
this.dnd.selectedRect = void 0;
|
|
46809
46961
|
this.dnd.horizontalSnap = void 0;
|
|
46810
46962
|
this.dnd.verticalSnap = void 0;
|
|
46811
|
-
this.dnd.
|
|
46963
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46812
46964
|
};
|
|
46813
46965
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46814
46966
|
}
|
|
@@ -46864,7 +47016,7 @@ var FiguresContainer = class extends Component {
|
|
|
46864
47016
|
this.dnd.selectedRect = void 0;
|
|
46865
47017
|
this.dnd.horizontalSnap = void 0;
|
|
46866
47018
|
this.dnd.verticalSnap = void 0;
|
|
46867
|
-
this.dnd.
|
|
47019
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46868
47020
|
};
|
|
46869
47021
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46870
47022
|
}
|
|
@@ -46874,12 +47026,12 @@ var FiguresContainer = class extends Component {
|
|
|
46874
47026
|
getFigureStyle(figureUI) {
|
|
46875
47027
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
46876
47028
|
return cssPropertiesToCss({
|
|
46877
|
-
opacity: this.dnd.
|
|
47029
|
+
opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
|
|
46878
47030
|
cursor: "grabbing"
|
|
46879
47031
|
});
|
|
46880
47032
|
}
|
|
46881
47033
|
getFigureClass(figureUI) {
|
|
46882
|
-
if (figureUI.id !== this.dnd.
|
|
47034
|
+
if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
|
|
46883
47035
|
return "o-add-to-carousel";
|
|
46884
47036
|
}
|
|
46885
47037
|
getSnap(snapLine) {
|
|
@@ -46922,18 +47074,18 @@ var FiguresContainer = class extends Component {
|
|
|
46922
47074
|
height: `100%`
|
|
46923
47075
|
});
|
|
46924
47076
|
}
|
|
46925
|
-
|
|
47077
|
+
getOverlappingFigure(figureUI, otherFigures) {
|
|
46926
47078
|
if (figureUI.tag !== "chart") return;
|
|
46927
47079
|
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
46928
47080
|
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
46929
47081
|
let bestMatch;
|
|
46930
47082
|
let smallestDistance = Infinity;
|
|
46931
47083
|
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 -
|
|
47084
|
+
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
47085
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
47086
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
47087
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
47088
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
46937
47089
|
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
46938
47090
|
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
46939
47091
|
smallestDistance = squaredDistance;
|
|
@@ -53026,11 +53178,16 @@ var DefaultPlugin = class extends CorePlugin {
|
|
|
53026
53178
|
const deltaStyle = {};
|
|
53027
53179
|
let hasDelta = false;
|
|
53028
53180
|
const styleSheet = this.style[position.sheetId];
|
|
53029
|
-
if (!styleSheet) continue;
|
|
53030
53181
|
for (const key in newDefaultStyle) {
|
|
53031
53182
|
if (key in cellStyle) continue;
|
|
53032
|
-
const defaults = styleSheet[key];
|
|
53033
|
-
if (!defaults)
|
|
53183
|
+
const defaults = styleSheet?.[key];
|
|
53184
|
+
if (!defaults) {
|
|
53185
|
+
if (newDefaultStyle[key] !== DEFAULT_STYLE[key]) {
|
|
53186
|
+
deltaStyle[key] = DEFAULT_STYLE[key];
|
|
53187
|
+
hasDelta = true;
|
|
53188
|
+
}
|
|
53189
|
+
continue;
|
|
53190
|
+
}
|
|
53034
53191
|
const rowDefault = defaults.rowDefault?.[position.row];
|
|
53035
53192
|
if (rowDefault !== void 0) {
|
|
53036
53193
|
if (priorities.shouldUseDefaultRow) {
|
|
@@ -53072,7 +53229,8 @@ var DefaultPlugin = class extends CorePlugin {
|
|
|
53072
53229
|
for (const key in styleSheet) if (!(key in style)) {
|
|
53073
53230
|
const defaults = styleSheet[key];
|
|
53074
53231
|
if (!defaults) continue;
|
|
53075
|
-
|
|
53232
|
+
const styleValue = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
|
|
53233
|
+
if (styleValue !== void 0) style[key] = styleValue;
|
|
53076
53234
|
}
|
|
53077
53235
|
return style;
|
|
53078
53236
|
}
|
|
@@ -58904,13 +59062,13 @@ var EvaluationConditionalFormatPlugin = class extends CoreViewPlugin {
|
|
|
58904
59062
|
const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
|
|
58905
59063
|
const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
|
|
58906
59064
|
const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
|
|
58907
|
-
if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
|
|
59065
|
+
if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58908
59066
|
const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
|
|
58909
59067
|
const colorThresholds = [{
|
|
58910
59068
|
value: minValue,
|
|
58911
59069
|
color: rule.minimum.color
|
|
58912
59070
|
}];
|
|
58913
|
-
if (rule.midpoint && midValue) colorThresholds.push({
|
|
59071
|
+
if (rule.midpoint && midValue !== null) colorThresholds.push({
|
|
58914
59072
|
value: midValue,
|
|
58915
59073
|
color: rule.midpoint.color
|
|
58916
59074
|
});
|
|
@@ -66369,7 +66527,9 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66369
66527
|
ctx.lineWidth = 1.5 * thinLineWidth;
|
|
66370
66528
|
const isDarkMode = this.getters.isDarkMode();
|
|
66371
66529
|
for (const zone of zones) {
|
|
66530
|
+
if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
|
|
66372
66531
|
const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
|
|
66532
|
+
const currentLineWidth = ctx.lineWidth;
|
|
66373
66533
|
if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
|
|
66374
66534
|
if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
|
|
66375
66535
|
if (height === 0 && width === 0) {
|
|
@@ -66383,6 +66543,7 @@ var GridSelectionPlugin = class extends UIPlugin {
|
|
|
66383
66543
|
ctx.globalCompositeOperation = "source-over";
|
|
66384
66544
|
ctx.strokeRect(x, y, width, height);
|
|
66385
66545
|
}
|
|
66546
|
+
ctx.lineWidth = currentLineWidth;
|
|
66386
66547
|
}
|
|
66387
66548
|
ctx.globalCompositeOperation = "source-over";
|
|
66388
66549
|
const position = renderingContext.activePosition;
|
|
@@ -66529,6 +66690,9 @@ var InternalViewport = class {
|
|
|
66529
66690
|
this.adjustViewportZoneX();
|
|
66530
66691
|
this.adjustViewportZoneY();
|
|
66531
66692
|
}
|
|
66693
|
+
isZoneVisible(zone) {
|
|
66694
|
+
return intersection(zone, this) !== void 0;
|
|
66695
|
+
}
|
|
66532
66696
|
/**
|
|
66533
66697
|
*
|
|
66534
66698
|
* Computes the visible coordinates & dimensions of a given zone inside the viewport
|
|
@@ -66809,6 +66973,9 @@ var ViewportCollection = class {
|
|
|
66809
66973
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
66810
66974
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
66811
66975
|
}
|
|
66976
|
+
isZoneVisibleInViewport(sheetId, zone) {
|
|
66977
|
+
return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
|
|
66978
|
+
}
|
|
66812
66979
|
getScrollBarWidth() {
|
|
66813
66980
|
return 15 / this.zoomLevel;
|
|
66814
66981
|
}
|
|
@@ -70882,18 +71049,28 @@ var NamedRangeSelector = class extends Component {
|
|
|
70882
71049
|
return;
|
|
70883
71050
|
}
|
|
70884
71051
|
const activeSheetId = this.env.model.getters.getActiveSheetId();
|
|
70885
|
-
if (activeSheetId !== sheetId)
|
|
70886
|
-
|
|
70887
|
-
|
|
70888
|
-
|
|
70889
|
-
|
|
71052
|
+
if (activeSheetId !== sheetId) {
|
|
71053
|
+
if (!this.env.model.getters.getSheet(sheetId).isVisible) {
|
|
71054
|
+
this.env.notifyUser({
|
|
71055
|
+
text: _t("The sheet on which the range is defined is hidden."),
|
|
71056
|
+
type: "info",
|
|
71057
|
+
sticky: false
|
|
71058
|
+
});
|
|
71059
|
+
return;
|
|
71060
|
+
}
|
|
71061
|
+
this.env.model.dispatch("ACTIVATE_SHEET", {
|
|
71062
|
+
sheetIdFrom: activeSheetId,
|
|
71063
|
+
sheetIdTo: sheetId
|
|
71064
|
+
});
|
|
71065
|
+
}
|
|
71066
|
+
this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
|
|
70890
71067
|
this.env.model.selection.selectZone({
|
|
70891
71068
|
cell: {
|
|
70892
71069
|
col: zone.left,
|
|
70893
71070
|
row: zone.top
|
|
70894
71071
|
},
|
|
70895
71072
|
zone
|
|
70896
|
-
});
|
|
71073
|
+
}, { allowsHiddenSelection: true });
|
|
70897
71074
|
}
|
|
70898
71075
|
get selectionKey() {
|
|
70899
71076
|
return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
|
|
@@ -77172,8 +77349,8 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
77172
77349
|
for (const key in DEFAULT_STYLE) {
|
|
77173
77350
|
content.style[key] = {
|
|
77174
77351
|
sheetDefault: this.getters.getDefaultStyle(data.sheetId, key, "SHEET", void 0) ?? DEFAULT_STYLE[key],
|
|
77175
|
-
colDefault:
|
|
77176
|
-
rowDefault:
|
|
77352
|
+
colDefault: [],
|
|
77353
|
+
rowDefault: []
|
|
77177
77354
|
};
|
|
77178
77355
|
let colIndex = 0;
|
|
77179
77356
|
for (const col of data.columnsIndexes) {
|
|
@@ -77190,20 +77367,41 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
77190
77367
|
}
|
|
77191
77368
|
return content;
|
|
77192
77369
|
}
|
|
77193
|
-
|
|
77194
|
-
|
|
77195
|
-
|
|
77196
|
-
|
|
77197
|
-
|
|
77198
|
-
|
|
77370
|
+
adaptContentToZone(zone, content) {
|
|
77371
|
+
const colRepetition = Math.max(Math.floor((zone.right - zone.left + 1) / content.width), 1);
|
|
77372
|
+
const rowRepetition = Math.max(Math.floor((zone.bottom - zone.top + 1) / content.height), 1);
|
|
77373
|
+
if (colRepetition === 1 && rowRepetition === 1) return content;
|
|
77374
|
+
const newContent = deepCopy(content);
|
|
77375
|
+
newContent.height *= rowRepetition;
|
|
77376
|
+
newContent.width *= colRepetition;
|
|
77377
|
+
if (rowRepetition > 1 && newContent.format.rowDefault) {
|
|
77378
|
+
newContent.format.rowDefault.length = content.height;
|
|
77379
|
+
newContent.format.rowDefault = repeat(newContent.format.rowDefault, rowRepetition);
|
|
77380
|
+
}
|
|
77381
|
+
if (colRepetition > 1 && newContent.format.colDefault) {
|
|
77382
|
+
newContent.format.colDefault.length = content.width;
|
|
77383
|
+
newContent.format.colDefault = repeat(newContent.format.colDefault, colRepetition);
|
|
77384
|
+
}
|
|
77385
|
+
for (const key in content.style) {
|
|
77386
|
+
if (rowRepetition > 1 && newContent.style[key].rowDefault) {
|
|
77387
|
+
newContent.style[key].rowDefault.length = content.height;
|
|
77388
|
+
newContent.style[key].rowDefault = repeat(newContent.style[key].rowDefault, rowRepetition);
|
|
77389
|
+
}
|
|
77390
|
+
if (colRepetition > 1 && newContent.style[key].colDefault) {
|
|
77391
|
+
newContent.style[key].colDefault.length = content.width;
|
|
77392
|
+
newContent.style[key].colDefault = repeat(newContent.style[key].colDefault, colRepetition);
|
|
77393
|
+
}
|
|
77394
|
+
}
|
|
77395
|
+
return newContent;
|
|
77199
77396
|
}
|
|
77200
77397
|
paste(target, content, options) {
|
|
77201
77398
|
const sheetId = target.sheetId;
|
|
77202
77399
|
if (options.pasteOption === "asValue") return;
|
|
77203
77400
|
const zones = target.zones;
|
|
77204
|
-
if (!options.isCutOperation) for (const zone of zones)
|
|
77205
|
-
|
|
77206
|
-
this.
|
|
77401
|
+
if (!options.isCutOperation) for (const zone of zones) {
|
|
77402
|
+
const newContent = this.adaptContentToZone(zone, content);
|
|
77403
|
+
this.pasteStyle(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.style);
|
|
77404
|
+
this.pasteFormat(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.format);
|
|
77207
77405
|
}
|
|
77208
77406
|
else {
|
|
77209
77407
|
this.clearClippedZones(content);
|
|
@@ -77316,7 +77514,7 @@ var DefaultClipboardHandler = class extends AbstractCellClipboardHandler {
|
|
|
77316
77514
|
const commands = [];
|
|
77317
77515
|
for (const [zoneStr, [zone, priority]] of Object.entries(zones)) {
|
|
77318
77516
|
const format = updateCells[zoneStr];
|
|
77319
|
-
commands.push([
|
|
77517
|
+
if (format !== void 0) commands.push([
|
|
77320
77518
|
priority,
|
|
77321
77519
|
zone,
|
|
77322
77520
|
format
|
|
@@ -87518,7 +87716,9 @@ const helpers = {
|
|
|
87518
87716
|
collapseHierarchicalDisplayName,
|
|
87519
87717
|
getCanonicalSymbolName,
|
|
87520
87718
|
fuzzyLookup,
|
|
87521
|
-
replaceSymbolInFormula
|
|
87719
|
+
replaceSymbolInFormula,
|
|
87720
|
+
isSingleCellReference,
|
|
87721
|
+
computeCachedTextDimension
|
|
87522
87722
|
};
|
|
87523
87723
|
const links = {
|
|
87524
87724
|
isMarkdownLink,
|
|
@@ -87588,7 +87788,8 @@ const components = {
|
|
|
87588
87788
|
FullScreenFigure,
|
|
87589
87789
|
NumberInput,
|
|
87590
87790
|
TopBar,
|
|
87591
|
-
Composer
|
|
87791
|
+
Composer,
|
|
87792
|
+
CalendarButton
|
|
87592
87793
|
};
|
|
87593
87794
|
const hooks = {
|
|
87594
87795
|
useDragAndDropListItems,
|
|
@@ -87742,6 +87943,6 @@ exports.stores = stores;
|
|
|
87742
87943
|
exports.tokenColors = tokenColors;
|
|
87743
87944
|
exports.tokenize = tokenize;
|
|
87744
87945
|
|
|
87745
|
-
__info__.version = "19.5.0-alpha.
|
|
87746
|
-
__info__.date = "2026-07-
|
|
87747
|
-
__info__.hash = "
|
|
87946
|
+
__info__.version = "19.5.0-alpha.3";
|
|
87947
|
+
__info__.date = "2026-07-14T10:17:06.949Z";
|
|
87948
|
+
__info__.hash = "c029184";
|