@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
|
@@ -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
|
(function(exports, _odoo_owl) {
|
|
@@ -564,6 +564,7 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
564
564
|
};
|
|
565
565
|
const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
|
|
566
566
|
const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
|
|
567
|
+
const DEFAULT_TEXT_HIGHLIGHT_PERCENT = .25;
|
|
567
568
|
const DEFAULT_FONT_SIZE = DEFAULT_STYLE.fontSize;
|
|
568
569
|
const DEFAULT_FONT = "'Roboto', arial, 'Liberation Sans'";
|
|
569
570
|
const DEFAULT_BORDER_DESC = {
|
|
@@ -1248,6 +1249,12 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
1248
1249
|
}
|
|
1249
1250
|
};
|
|
1250
1251
|
}
|
|
1252
|
+
function repeat(array, times) {
|
|
1253
|
+
const len = array.length;
|
|
1254
|
+
const result = new Array(len * times);
|
|
1255
|
+
for (let t = 0; t < times; t++) for (let i = 0; i < len; i++) result[t * len + i] = array[i];
|
|
1256
|
+
return result;
|
|
1257
|
+
}
|
|
1251
1258
|
|
|
1252
1259
|
//#endregion
|
|
1253
1260
|
//#region src/helpers/coordinates.ts
|
|
@@ -2601,7 +2608,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2601
2608
|
*
|
|
2602
2609
|
* The end goal is to eliminate all compatibility shims.
|
|
2603
2610
|
*/
|
|
2604
|
-
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$24, onPatched: onPatched$5, onWillUnmount: onWillUnmount$18, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props: props$
|
|
2611
|
+
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$24, onPatched: onPatched$5, onWillUnmount: onWillUnmount$18, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props: props$167, providePlugins, useScope, xml: xml$4 } = _odoo_owl;
|
|
2605
2612
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2606
2613
|
var _Component = class extends OwlComponent {
|
|
2607
2614
|
static template = "";
|
|
@@ -2731,7 +2738,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2731
2738
|
static template = xml$4`<t t-call-slot="default"/>`;
|
|
2732
2739
|
constructor(node) {
|
|
2733
2740
|
super(node);
|
|
2734
|
-
this.props = props$
|
|
2741
|
+
this.props = props$167();
|
|
2735
2742
|
this.__owl__ = node;
|
|
2736
2743
|
}
|
|
2737
2744
|
setup() {
|
|
@@ -8407,534 +8414,6 @@ set(value) {
|
|
|
8407
8414
|
return cell;
|
|
8408
8415
|
}
|
|
8409
8416
|
|
|
8410
|
-
//#endregion
|
|
8411
|
-
//#region src/helpers/text_helper.ts
|
|
8412
|
-
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
8413
|
-
return numberOfLines * (textLineHeight + 4) - 4;
|
|
8414
|
-
}
|
|
8415
|
-
function getCanvas(width = 100, height = 100) {
|
|
8416
|
-
return new OffscreenCanvas(width, height).getContext("2d");
|
|
8417
|
-
}
|
|
8418
|
-
/**
|
|
8419
|
-
* Get the default height of the cell given its style.
|
|
8420
|
-
*/
|
|
8421
|
-
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
8422
|
-
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
8423
|
-
let content = "";
|
|
8424
|
-
try {
|
|
8425
|
-
if (!cell.isFormula) {
|
|
8426
|
-
const localeFormat = {
|
|
8427
|
-
format: cell.format,
|
|
8428
|
-
locale
|
|
8429
|
-
};
|
|
8430
|
-
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
8431
|
-
}
|
|
8432
|
-
} catch {
|
|
8433
|
-
content = CellErrorType.GenericError;
|
|
8434
|
-
}
|
|
8435
|
-
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
8436
|
-
}
|
|
8437
|
-
function getCellContentHeight(ctx, content, style, colSize) {
|
|
8438
|
-
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
8439
|
-
}
|
|
8440
|
-
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
8441
|
-
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
8442
|
-
}
|
|
8443
|
-
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
8444
|
-
if (!textLines.length) return {
|
|
8445
|
-
width: 0,
|
|
8446
|
-
height: 0
|
|
8447
|
-
};
|
|
8448
|
-
const font = computeTextFont(style, fontUnit);
|
|
8449
|
-
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
8450
|
-
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
8451
|
-
const width = Math.max(...sizes.map((size) => size.width));
|
|
8452
|
-
if (!style.rotation) return {
|
|
8453
|
-
height,
|
|
8454
|
-
width
|
|
8455
|
-
};
|
|
8456
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8457
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8458
|
-
return {
|
|
8459
|
-
width: width * cos + height * sin,
|
|
8460
|
-
height: sin * width + cos * height
|
|
8461
|
-
};
|
|
8462
|
-
}
|
|
8463
|
-
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
8464
|
-
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
8465
|
-
}
|
|
8466
|
-
function computeCachedTextWidth(context, text, font, rotation) {
|
|
8467
|
-
const size = computeCachedTextDimension(context, text, font);
|
|
8468
|
-
if (!rotation) return size.width;
|
|
8469
|
-
const cos = Math.abs(Math.cos(rotation));
|
|
8470
|
-
const sin = Math.abs(Math.sin(rotation));
|
|
8471
|
-
return size.width * cos + size.height * sin;
|
|
8472
|
-
}
|
|
8473
|
-
const textDimensionsCache = {};
|
|
8474
|
-
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
8475
|
-
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
8476
|
-
if (!style.rotation) return size;
|
|
8477
|
-
const cos = Math.abs(Math.cos(style.rotation));
|
|
8478
|
-
const sin = Math.abs(Math.sin(style.rotation));
|
|
8479
|
-
return {
|
|
8480
|
-
width: size.width * cos + size.height * sin,
|
|
8481
|
-
height: size.height * cos + size.width * sin
|
|
8482
|
-
};
|
|
8483
|
-
}
|
|
8484
|
-
function computeCachedTextDimension(context, text, font) {
|
|
8485
|
-
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
8486
|
-
if (textDimensionsCache[font][text] === void 0) {
|
|
8487
|
-
context.save();
|
|
8488
|
-
context.font = font;
|
|
8489
|
-
const measure = context.measureText(text);
|
|
8490
|
-
context.restore();
|
|
8491
|
-
const width = measure.width;
|
|
8492
|
-
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8493
|
-
textDimensionsCache[font][text] = {
|
|
8494
|
-
width,
|
|
8495
|
-
height
|
|
8496
|
-
};
|
|
8497
|
-
}
|
|
8498
|
-
return textDimensionsCache[font][text];
|
|
8499
|
-
}
|
|
8500
|
-
function fontSizeInPixels(fontSize) {
|
|
8501
|
-
return Math.round(fontSize * 96 / 72);
|
|
8502
|
-
}
|
|
8503
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
8504
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
8505
|
-
}
|
|
8506
|
-
function computeTextFontSizeInPixels(style) {
|
|
8507
|
-
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
8508
|
-
}
|
|
8509
|
-
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
8510
|
-
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
8511
|
-
const splitWord = [];
|
|
8512
|
-
let wordPart = "";
|
|
8513
|
-
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
8514
|
-
splitWord.push(wordPart);
|
|
8515
|
-
wordPart = l;
|
|
8516
|
-
} else wordPart += l;
|
|
8517
|
-
splitWord.push(wordPart);
|
|
8518
|
-
return splitWord;
|
|
8519
|
-
}
|
|
8520
|
-
/**
|
|
8521
|
-
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
8522
|
-
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
8523
|
-
*/
|
|
8524
|
-
function splitTextToWidth(ctx, text, style, width) {
|
|
8525
|
-
if (!style) style = {};
|
|
8526
|
-
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
8527
|
-
const brokenText = [];
|
|
8528
|
-
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
8529
|
-
for (const line of lines) {
|
|
8530
|
-
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
8531
|
-
if (!width) {
|
|
8532
|
-
brokenText.push(line);
|
|
8533
|
-
continue;
|
|
8534
|
-
}
|
|
8535
|
-
let textLine = "";
|
|
8536
|
-
let availableWidth = width;
|
|
8537
|
-
for (const word of words) {
|
|
8538
|
-
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
8539
|
-
const lastPart = splitWord.pop();
|
|
8540
|
-
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
8541
|
-
if (splitWord.length) {
|
|
8542
|
-
if (textLine !== "") {
|
|
8543
|
-
brokenText.push(textLine);
|
|
8544
|
-
textLine = "";
|
|
8545
|
-
availableWidth = width;
|
|
8546
|
-
}
|
|
8547
|
-
splitWord.forEach((wordPart) => {
|
|
8548
|
-
brokenText.push(wordPart);
|
|
8549
|
-
});
|
|
8550
|
-
textLine = lastPart;
|
|
8551
|
-
availableWidth = width - lastPartWidth;
|
|
8552
|
-
} else {
|
|
8553
|
-
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
8554
|
-
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
8555
|
-
if (wordWidth <= availableWidth) {
|
|
8556
|
-
textLine += _word;
|
|
8557
|
-
availableWidth -= wordWidth;
|
|
8558
|
-
} else {
|
|
8559
|
-
brokenText.push(textLine);
|
|
8560
|
-
textLine = lastPart;
|
|
8561
|
-
availableWidth = width - lastPartWidth;
|
|
8562
|
-
}
|
|
8563
|
-
}
|
|
8564
|
-
}
|
|
8565
|
-
if (textLine !== "") brokenText.push(textLine);
|
|
8566
|
-
}
|
|
8567
|
-
return brokenText;
|
|
8568
|
-
}
|
|
8569
|
-
/**
|
|
8570
|
-
* Return the font size that makes the width of a text match the given line width.
|
|
8571
|
-
* Minimum font size is 1.
|
|
8572
|
-
*
|
|
8573
|
-
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
8574
|
-
*/
|
|
8575
|
-
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
8576
|
-
let minFontSize = 1;
|
|
8577
|
-
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
8578
|
-
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
8579
|
-
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
8580
|
-
let currentTextWidth = getTextWidth(fontSize);
|
|
8581
|
-
let iterations = 0;
|
|
8582
|
-
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
8583
|
-
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
8584
|
-
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
8585
|
-
fontSize = (minFontSize + maxFontSize) / 2;
|
|
8586
|
-
currentTextWidth = getTextWidth(fontSize);
|
|
8587
|
-
iterations++;
|
|
8588
|
-
}
|
|
8589
|
-
return fontSize;
|
|
8590
|
-
}
|
|
8591
|
-
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
8592
|
-
function toTrimmedLowerCase(str) {
|
|
8593
|
-
return str ? str.toLowerCase().trim() : "";
|
|
8594
|
-
}
|
|
8595
|
-
/**
|
|
8596
|
-
* Extract the fontSize from a context font string
|
|
8597
|
-
* @param font The (context) font string to parse
|
|
8598
|
-
* @returns The fontSize in pixels
|
|
8599
|
-
*/
|
|
8600
|
-
const pxRegex = /([0-9\.]*)px/;
|
|
8601
|
-
function getContextFontSize(font) {
|
|
8602
|
-
return Number(font.match(pxRegex)?.[1]);
|
|
8603
|
-
}
|
|
8604
|
-
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
8605
|
-
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8606
|
-
if (width <= maxWidth) return text;
|
|
8607
|
-
const ellipsis = "…";
|
|
8608
|
-
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
8609
|
-
if (width <= ellipsisWidth) return text;
|
|
8610
|
-
let len = text.length;
|
|
8611
|
-
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
8612
|
-
text = text.substring(0, len);
|
|
8613
|
-
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
8614
|
-
}
|
|
8615
|
-
return text + ellipsis;
|
|
8616
|
-
}
|
|
8617
|
-
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10) {
|
|
8618
|
-
context.fillText(text, position.x, position.y);
|
|
8619
|
-
if (!underline && !strikethrough) return;
|
|
8620
|
-
const measure = context.measureText(text);
|
|
8621
|
-
const textWidth = measure.width;
|
|
8622
|
-
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
8623
|
-
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
8624
|
-
let { x, y } = position;
|
|
8625
|
-
let strikeY = y, underlineY = y;
|
|
8626
|
-
switch (context.textAlign) {
|
|
8627
|
-
case "center":
|
|
8628
|
-
x -= textWidth / 2;
|
|
8629
|
-
break;
|
|
8630
|
-
case "right":
|
|
8631
|
-
x -= textWidth;
|
|
8632
|
-
break;
|
|
8633
|
-
}
|
|
8634
|
-
switch (context.textBaseline) {
|
|
8635
|
-
case "top":
|
|
8636
|
-
underlineY += boxHeight - 2 * strokeWidth;
|
|
8637
|
-
strikeY += boxHeight / 2 - strokeWidth;
|
|
8638
|
-
break;
|
|
8639
|
-
case "middle":
|
|
8640
|
-
underlineY += boxHeight / 2 - strokeWidth;
|
|
8641
|
-
break;
|
|
8642
|
-
case "alphabetic":
|
|
8643
|
-
underlineY += 2 * strokeWidth;
|
|
8644
|
-
strikeY -= 3 * strokeWidth;
|
|
8645
|
-
break;
|
|
8646
|
-
case "bottom":
|
|
8647
|
-
underlineY = y;
|
|
8648
|
-
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
8649
|
-
break;
|
|
8650
|
-
}
|
|
8651
|
-
if (underline) {
|
|
8652
|
-
context.lineWidth = strokeWidth;
|
|
8653
|
-
context.strokeStyle = context.fillStyle;
|
|
8654
|
-
context.beginPath();
|
|
8655
|
-
context.moveTo(x, underlineY);
|
|
8656
|
-
context.lineTo(x + textWidth, underlineY);
|
|
8657
|
-
context.stroke();
|
|
8658
|
-
}
|
|
8659
|
-
if (strikethrough) {
|
|
8660
|
-
context.lineWidth = strokeWidth;
|
|
8661
|
-
context.strokeStyle = context.fillStyle;
|
|
8662
|
-
context.beginPath();
|
|
8663
|
-
context.moveTo(x, strikeY);
|
|
8664
|
-
context.lineTo(x + textWidth, strikeY);
|
|
8665
|
-
context.stroke();
|
|
8666
|
-
}
|
|
8667
|
-
}
|
|
8668
|
-
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
8669
|
-
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
8670
|
-
const ellipsis = "...";
|
|
8671
|
-
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
8672
|
-
if (ellipsisWidth >= width) return "";
|
|
8673
|
-
let lowerBoundLen = 1;
|
|
8674
|
-
let upperBoundLen = text.length;
|
|
8675
|
-
let currentWidth;
|
|
8676
|
-
while (lowerBoundLen <= upperBoundLen) {
|
|
8677
|
-
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
8678
|
-
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
8679
|
-
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
8680
|
-
else lowerBoundLen = currentLen + 1;
|
|
8681
|
-
}
|
|
8682
|
-
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
8683
|
-
return slicedText ? slicedText + ellipsis : "";
|
|
8684
|
-
}
|
|
8685
|
-
/**
|
|
8686
|
-
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
8687
|
-
* with to original's text vertical and horizontal alignment.
|
|
8688
|
-
*/
|
|
8689
|
-
function computeRotationPosition(rect, style) {
|
|
8690
|
-
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
8691
|
-
let { x, y } = rect;
|
|
8692
|
-
const cos = Math.cos(-style.rotation);
|
|
8693
|
-
const sin = Math.sin(-style.rotation);
|
|
8694
|
-
const width = rect.textWidth - 2 * 4;
|
|
8695
|
-
const height = rect.textHeight;
|
|
8696
|
-
const center = style.align === "center";
|
|
8697
|
-
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
8698
|
-
const sh = sin * height;
|
|
8699
|
-
const sw = Math.abs(sin * width);
|
|
8700
|
-
const ch = cos * height;
|
|
8701
|
-
if (style.verticalAlign === "top") if (center) {
|
|
8702
|
-
y += sw / 2;
|
|
8703
|
-
x -= sh / 2;
|
|
8704
|
-
} else if (rotateTowardCellCenter) x -= sh;
|
|
8705
|
-
else y += sw;
|
|
8706
|
-
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
8707
|
-
y += height - ch;
|
|
8708
|
-
if (center) {
|
|
8709
|
-
y -= sw / 2;
|
|
8710
|
-
x -= sh / 2;
|
|
8711
|
-
} else if (rotateTowardCellCenter) {
|
|
8712
|
-
x -= sh;
|
|
8713
|
-
y -= sw;
|
|
8714
|
-
}
|
|
8715
|
-
} else if (center) {
|
|
8716
|
-
x -= sh / 2;
|
|
8717
|
-
y -= height / 2;
|
|
8718
|
-
if (rotateTowardCellCenter) y += sh;
|
|
8719
|
-
else y -= sh;
|
|
8720
|
-
} else if (rotateTowardCellCenter) {
|
|
8721
|
-
x -= sh;
|
|
8722
|
-
y -= sw / 2;
|
|
8723
|
-
} else y += sw / 2 + ch / 4;
|
|
8724
|
-
return {
|
|
8725
|
-
x: cos * x - sin * y,
|
|
8726
|
-
y: cos * y + sin * x
|
|
8727
|
-
};
|
|
8728
|
-
}
|
|
8729
|
-
|
|
8730
|
-
//#endregion
|
|
8731
|
-
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
8732
|
-
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
8733
|
-
const chartColorScalePlugin = {
|
|
8734
|
-
id: "chartColorScalePlugin",
|
|
8735
|
-
afterDatasetsDraw(chart, args, options) {
|
|
8736
|
-
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
8737
|
-
const ctx = chart.ctx;
|
|
8738
|
-
ctx.save();
|
|
8739
|
-
ctx.textAlign = "center";
|
|
8740
|
-
ctx.textBaseline = "middle";
|
|
8741
|
-
ctx.miterLimit = 1;
|
|
8742
|
-
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
8743
|
-
const gradientWidth = 10;
|
|
8744
|
-
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
8745
|
-
const gradientY = chart.chartArea.top;
|
|
8746
|
-
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
8747
|
-
const step = 1 / (options.colorScale.length - 1);
|
|
8748
|
-
options.colorScale.forEach((color, index) => {
|
|
8749
|
-
gradient.addColorStop(index * step, color);
|
|
8750
|
-
});
|
|
8751
|
-
ctx.fillStyle = gradient;
|
|
8752
|
-
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
8753
|
-
ctx.fillStyle = options.fontColor ?? "black";
|
|
8754
|
-
ctx.font = getDefaultContextFont(12);
|
|
8755
|
-
ctx.textAlign = "left";
|
|
8756
|
-
let minValue = Math.round(options.minValue * 100) / 100;
|
|
8757
|
-
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
8758
|
-
if (options.minValue === options.maxValue) {
|
|
8759
|
-
minValue -= 1;
|
|
8760
|
-
maxValue += 1;
|
|
8761
|
-
}
|
|
8762
|
-
const formattedMaxValue = humanizeNumber({
|
|
8763
|
-
value: maxValue,
|
|
8764
|
-
format: void 0
|
|
8765
|
-
}, options.locale);
|
|
8766
|
-
const formattedMinValue = humanizeNumber({
|
|
8767
|
-
value: minValue,
|
|
8768
|
-
format: void 0
|
|
8769
|
-
}, options.locale);
|
|
8770
|
-
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
8771
|
-
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
8772
|
-
ctx.restore();
|
|
8773
|
-
}
|
|
8774
|
-
};
|
|
8775
|
-
|
|
8776
|
-
//#endregion
|
|
8777
|
-
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
8778
|
-
function getFunnelChartController() {
|
|
8779
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8780
|
-
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
8781
|
-
static id = "funnel";
|
|
8782
|
-
static defaults = {
|
|
8783
|
-
...globalThis.Chart?.BarController.defaults,
|
|
8784
|
-
dataElementType: "funnel",
|
|
8785
|
-
animation: { duration: (ctx) => {
|
|
8786
|
-
if (ctx.type !== "data") return 1e3;
|
|
8787
|
-
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
8788
|
-
} }
|
|
8789
|
-
};
|
|
8790
|
-
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
8791
|
-
updateElements(rects, start, count, mode) {
|
|
8792
|
-
super.updateElements(rects, start, count, mode);
|
|
8793
|
-
for (let i = start; i < start + count; i++) {
|
|
8794
|
-
const rect = rects[i];
|
|
8795
|
-
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
8796
|
-
}
|
|
8797
|
-
}
|
|
8798
|
-
};
|
|
8799
|
-
}
|
|
8800
|
-
function getFunnelChartElement() {
|
|
8801
|
-
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
8802
|
-
/**
|
|
8803
|
-
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
8804
|
-
* `width`, and the bottom is of width `nextElementWidth`.
|
|
8805
|
-
*/
|
|
8806
|
-
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
8807
|
-
static id = "funnel";
|
|
8808
|
-
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
8809
|
-
draw(ctx) {
|
|
8810
|
-
ctx.save();
|
|
8811
|
-
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
8812
|
-
"x",
|
|
8813
|
-
"y",
|
|
8814
|
-
"width",
|
|
8815
|
-
"height",
|
|
8816
|
-
"nextElement",
|
|
8817
|
-
"base",
|
|
8818
|
-
"options"
|
|
8819
|
-
]);
|
|
8820
|
-
const width = getElementWidth(this);
|
|
8821
|
-
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
8822
|
-
const startX = Math.min(x, base);
|
|
8823
|
-
const startY = y - height / 2;
|
|
8824
|
-
ctx.fillStyle = options.backgroundColor;
|
|
8825
|
-
ctx.beginPath();
|
|
8826
|
-
ctx.moveTo(startX, startY);
|
|
8827
|
-
ctx.lineTo(startX + width, startY);
|
|
8828
|
-
ctx.lineTo(startX + width - offset, startY + height);
|
|
8829
|
-
ctx.lineTo(startX + offset, startY + height);
|
|
8830
|
-
ctx.closePath();
|
|
8831
|
-
ctx.fill();
|
|
8832
|
-
if (options.borderWidth) {
|
|
8833
|
-
ctx.strokeStyle = options.borderColor;
|
|
8834
|
-
ctx.lineWidth = options.borderWidth;
|
|
8835
|
-
ctx.stroke();
|
|
8836
|
-
}
|
|
8837
|
-
ctx.restore();
|
|
8838
|
-
}
|
|
8839
|
-
/** Check if the mouse is inside the trapezoid */
|
|
8840
|
-
inRange(mouseX, mouseY) {
|
|
8841
|
-
const { x, y, height, nextElement, base } = this.getProps([
|
|
8842
|
-
"x",
|
|
8843
|
-
"y",
|
|
8844
|
-
"width",
|
|
8845
|
-
"height",
|
|
8846
|
-
"nextElement",
|
|
8847
|
-
"base",
|
|
8848
|
-
"options"
|
|
8849
|
-
]);
|
|
8850
|
-
const width = getElementWidth(this);
|
|
8851
|
-
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
8852
|
-
const startX = Math.min(x, base);
|
|
8853
|
-
const startY = y - height / 2;
|
|
8854
|
-
if (mouseY < startY || mouseY > startY + height) return false;
|
|
8855
|
-
const offset = (width - nextElementWidth) / 2;
|
|
8856
|
-
const left = startX + offset * (mouseY - startY) / height;
|
|
8857
|
-
const right = startX + width - offset * (mouseY - startY) / height;
|
|
8858
|
-
if (mouseX < left || mouseX > right) return false;
|
|
8859
|
-
return true;
|
|
8860
|
-
}
|
|
8861
|
-
};
|
|
8862
|
-
}
|
|
8863
|
-
/**
|
|
8864
|
-
* Get an element width.
|
|
8865
|
-
*
|
|
8866
|
-
* The property width is undefined during animations, we need to compute it manually.
|
|
8867
|
-
*/
|
|
8868
|
-
function getElementWidth(element) {
|
|
8869
|
-
const { x, base } = element.getProps(["x", "base"]);
|
|
8870
|
-
return Math.max(x, base) - Math.min(x, base);
|
|
8871
|
-
}
|
|
8872
|
-
/**
|
|
8873
|
-
* Position the tooltip inside the trapezoid.
|
|
8874
|
-
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
8875
|
-
*/
|
|
8876
|
-
const funnelTooltipPositioner = function(elements) {
|
|
8877
|
-
if (!elements.length) return {
|
|
8878
|
-
x: 0,
|
|
8879
|
-
y: 0
|
|
8880
|
-
};
|
|
8881
|
-
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
8882
|
-
"x",
|
|
8883
|
-
"y",
|
|
8884
|
-
"width",
|
|
8885
|
-
"height",
|
|
8886
|
-
"base"
|
|
8887
|
-
]);
|
|
8888
|
-
const startX = Math.min(x, base);
|
|
8889
|
-
const startY = y - height / 2;
|
|
8890
|
-
return {
|
|
8891
|
-
x: startX + width * 2 / 3,
|
|
8892
|
-
y: startY + height / 2
|
|
8893
|
-
};
|
|
8894
|
-
};
|
|
8895
|
-
|
|
8896
|
-
//#endregion
|
|
8897
|
-
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
8898
|
-
const chartMinorGridPlugin = {
|
|
8899
|
-
id: "o-spreadsheet-minor-gridlines",
|
|
8900
|
-
beforeDatasetsDraw(chart) {
|
|
8901
|
-
const ctx = chart.ctx;
|
|
8902
|
-
const chartArea = chart.chartArea;
|
|
8903
|
-
if (!chartArea) return;
|
|
8904
|
-
for (const scaleId in chart.scales) {
|
|
8905
|
-
const scale = chart.scales[scaleId];
|
|
8906
|
-
const options = scale.options;
|
|
8907
|
-
const minor = options?.grid?.minor;
|
|
8908
|
-
if (!minor?.display) continue;
|
|
8909
|
-
const showMajorGrid = options?.grid?.display;
|
|
8910
|
-
const ticks = scale.ticks;
|
|
8911
|
-
if (!ticks || ticks.length < 2) continue;
|
|
8912
|
-
ctx.save();
|
|
8913
|
-
ctx.lineWidth = 1;
|
|
8914
|
-
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
8915
|
-
for (let i = 0; i < ticks.length - 1; i++) {
|
|
8916
|
-
const start = scale.getPixelForTick(i);
|
|
8917
|
-
const end = scale.getPixelForTick(i + 1);
|
|
8918
|
-
if (!isFinite(start) || !isFinite(end)) continue;
|
|
8919
|
-
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
8920
|
-
const ratio = j / 4;
|
|
8921
|
-
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
8922
|
-
ctx.beginPath();
|
|
8923
|
-
if (scale.isHorizontal()) {
|
|
8924
|
-
ctx.moveTo(position, chartArea.top);
|
|
8925
|
-
ctx.lineTo(position, chartArea.bottom);
|
|
8926
|
-
} else {
|
|
8927
|
-
ctx.moveTo(chartArea.left, position);
|
|
8928
|
-
ctx.lineTo(chartArea.right, position);
|
|
8929
|
-
}
|
|
8930
|
-
ctx.stroke();
|
|
8931
|
-
}
|
|
8932
|
-
}
|
|
8933
|
-
ctx.restore();
|
|
8934
|
-
}
|
|
8935
|
-
}
|
|
8936
|
-
};
|
|
8937
|
-
|
|
8938
8417
|
//#endregion
|
|
8939
8418
|
//#region src/helpers/color.ts
|
|
8940
8419
|
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
|
|
@@ -9528,113 +9007,646 @@ set(value) {
|
|
|
9528
9007
|
super(paletteSize, preferredColors);
|
|
9529
9008
|
this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
|
|
9530
9009
|
}
|
|
9531
|
-
};
|
|
9532
|
-
var AlternatingColorMap = class {
|
|
9533
|
-
availableColors;
|
|
9534
|
-
colors = {};
|
|
9535
|
-
constructor(paletteSize = 12) {
|
|
9536
|
-
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9010
|
+
};
|
|
9011
|
+
var AlternatingColorMap = class {
|
|
9012
|
+
availableColors;
|
|
9013
|
+
colors = {};
|
|
9014
|
+
constructor(paletteSize = 12) {
|
|
9015
|
+
this.availableColors = new AlternatingColorGenerator(paletteSize);
|
|
9016
|
+
}
|
|
9017
|
+
get(id) {
|
|
9018
|
+
if (!this.colors[id]) this.colors[id] = this.availableColors.next();
|
|
9019
|
+
return this.colors[id];
|
|
9020
|
+
}
|
|
9021
|
+
};
|
|
9022
|
+
const COLORSCHEMES = {
|
|
9023
|
+
greys: [
|
|
9024
|
+
"#ffffff",
|
|
9025
|
+
"#808080",
|
|
9026
|
+
"#000000"
|
|
9027
|
+
],
|
|
9028
|
+
blues: [
|
|
9029
|
+
"#f7fbff",
|
|
9030
|
+
"#6aaed6",
|
|
9031
|
+
"#08306b"
|
|
9032
|
+
],
|
|
9033
|
+
reds: [
|
|
9034
|
+
"#fff5f0",
|
|
9035
|
+
"#fb694a",
|
|
9036
|
+
"#67000d"
|
|
9037
|
+
],
|
|
9038
|
+
greens: [
|
|
9039
|
+
"#f7fcf5",
|
|
9040
|
+
"#73c476",
|
|
9041
|
+
"#00441b"
|
|
9042
|
+
],
|
|
9043
|
+
oranges: [
|
|
9044
|
+
"#fff5eb",
|
|
9045
|
+
"#fd8c3b",
|
|
9046
|
+
"#7f2704"
|
|
9047
|
+
],
|
|
9048
|
+
purples: [
|
|
9049
|
+
"#fcfbfd",
|
|
9050
|
+
"#9e9ac8",
|
|
9051
|
+
"#3f007d"
|
|
9052
|
+
],
|
|
9053
|
+
viridis: [
|
|
9054
|
+
"#440154",
|
|
9055
|
+
"#21918c",
|
|
9056
|
+
"#fde725"
|
|
9057
|
+
],
|
|
9058
|
+
cividis: [
|
|
9059
|
+
"#00224e",
|
|
9060
|
+
"#7d7c78",
|
|
9061
|
+
"#fee838"
|
|
9062
|
+
],
|
|
9063
|
+
rainbow: [
|
|
9064
|
+
"#B41DB4",
|
|
9065
|
+
"#FFFF00",
|
|
9066
|
+
"#00FFFF"
|
|
9067
|
+
]
|
|
9068
|
+
};
|
|
9069
|
+
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9070
|
+
/**
|
|
9071
|
+
* Returns a function that maps a value to a color using a color scale defined by the given
|
|
9072
|
+
* color/threshold values pairs.
|
|
9073
|
+
*/
|
|
9074
|
+
function getColorScale(colorScalePoints) {
|
|
9075
|
+
if (colorScalePoints.length < 2) throw new Error("Color scale must have at least 2 points");
|
|
9076
|
+
const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
|
|
9077
|
+
const thresholds = [];
|
|
9078
|
+
for (let i = 1; i < sortedColorScalePoints.length; i++) {
|
|
9079
|
+
const minColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i - 1].color).a;
|
|
9080
|
+
const maxColorAlpha = colorOrNumberToRGBA(sortedColorScalePoints[i].color).a;
|
|
9081
|
+
const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
|
|
9082
|
+
const maxColor = colorToNumber(sortedColorScalePoints[i].color);
|
|
9083
|
+
thresholds.push({
|
|
9084
|
+
min: sortedColorScalePoints[i - 1].value,
|
|
9085
|
+
max: sortedColorScalePoints[i].value,
|
|
9086
|
+
minColor,
|
|
9087
|
+
maxColor,
|
|
9088
|
+
minColorAlpha,
|
|
9089
|
+
maxColorAlpha,
|
|
9090
|
+
colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor)
|
|
9091
|
+
});
|
|
9092
|
+
}
|
|
9093
|
+
return (value) => {
|
|
9094
|
+
if (value < thresholds[0].min) return colorNumberToHex(thresholds[0].minColor, thresholds[0].minColorAlpha);
|
|
9095
|
+
for (const threshold of thresholds) if (value >= threshold.min && value <= threshold.max) return colorNumberToHex(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff), threshold.maxColorAlpha);
|
|
9096
|
+
return colorNumberToHex(thresholds[thresholds.length - 1].maxColor, thresholds[thresholds.length - 1].maxColorAlpha);
|
|
9097
|
+
};
|
|
9098
|
+
}
|
|
9099
|
+
function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
|
|
9100
|
+
const deltaValue = maxValue - minValue;
|
|
9101
|
+
const deltaColorR = (minColor >> 16) % 256 - (maxColor >> 16) % 256;
|
|
9102
|
+
const deltaColorG = (minColor >> 8) % 256 - (maxColor >> 8) % 256;
|
|
9103
|
+
const deltaColorB = minColor % 256 - maxColor % 256;
|
|
9104
|
+
return [
|
|
9105
|
+
deltaColorR / deltaValue,
|
|
9106
|
+
deltaColorG / deltaValue,
|
|
9107
|
+
deltaColorB / deltaValue
|
|
9108
|
+
];
|
|
9109
|
+
}
|
|
9110
|
+
function colorCell(value, minValue, minColor, colorDiffUnit) {
|
|
9111
|
+
const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
|
|
9112
|
+
const r = Math.round((minColor >> 16) % 256 - colorDiffUnitR * (value - minValue));
|
|
9113
|
+
const g = Math.round((minColor >> 8) % 256 - colorDiffUnitG * (value - minValue));
|
|
9114
|
+
const b = Math.round(minColor % 256 - colorDiffUnitB * (value - minValue));
|
|
9115
|
+
return r << 16 | g << 8 | b;
|
|
9116
|
+
}
|
|
9117
|
+
|
|
9118
|
+
//#endregion
|
|
9119
|
+
//#region src/helpers/text_helper.ts
|
|
9120
|
+
function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
9121
|
+
return numberOfLines * (textLineHeight + 4) - 4;
|
|
9122
|
+
}
|
|
9123
|
+
function getCanvas(width = 100, height = 100) {
|
|
9124
|
+
return new OffscreenCanvas(width, height).getContext("2d");
|
|
9125
|
+
}
|
|
9126
|
+
/**
|
|
9127
|
+
* Get the default height of the cell given its style.
|
|
9128
|
+
*/
|
|
9129
|
+
function getDefaultCellHeight(ctx, cell, locale, colSize) {
|
|
9130
|
+
if (!cell || !cell.isFormula && !cell.content) return 23;
|
|
9131
|
+
let content = "";
|
|
9132
|
+
try {
|
|
9133
|
+
if (!cell.isFormula) {
|
|
9134
|
+
const localeFormat = {
|
|
9135
|
+
format: cell.format,
|
|
9136
|
+
locale
|
|
9137
|
+
};
|
|
9138
|
+
content = formatValue(parseLiteral(cell.content, locale), localeFormat);
|
|
9139
|
+
}
|
|
9140
|
+
} catch {
|
|
9141
|
+
content = CellErrorType.GenericError;
|
|
9142
|
+
}
|
|
9143
|
+
return getCellContentHeight(ctx, content, cell.style, colSize);
|
|
9144
|
+
}
|
|
9145
|
+
function getCellContentHeight(ctx, content, style, colSize) {
|
|
9146
|
+
return computeMultilineTextSize(ctx, splitTextToWidth(ctx, content, style, style?.wrapping === "wrap" ? colSize - 2 * 4 : void 0), style).height + 2 * 3;
|
|
9147
|
+
}
|
|
9148
|
+
function getDefaultContextFont(fontSize, bold = false, italic = false) {
|
|
9149
|
+
return `${italic ? "italic" : ""} ${bold ? "bold" : ""} ${fontSize}px ${DEFAULT_FONT}`;
|
|
9150
|
+
}
|
|
9151
|
+
function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
|
|
9152
|
+
if (!textLines.length) return {
|
|
9153
|
+
width: 0,
|
|
9154
|
+
height: 0
|
|
9155
|
+
};
|
|
9156
|
+
const font = computeTextFont(style, fontUnit);
|
|
9157
|
+
const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
|
|
9158
|
+
const height = computeTextLinesHeight(sizes[0].height, textLines.length);
|
|
9159
|
+
const width = Math.max(...sizes.map((size) => size.width));
|
|
9160
|
+
if (!style.rotation) return {
|
|
9161
|
+
height,
|
|
9162
|
+
width
|
|
9163
|
+
};
|
|
9164
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9165
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9166
|
+
return {
|
|
9167
|
+
width: width * cos + height * sin,
|
|
9168
|
+
height: sin * width + cos * height
|
|
9169
|
+
};
|
|
9170
|
+
}
|
|
9171
|
+
function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
|
|
9172
|
+
return computeCachedTextWidth(context, text, computeTextFont(style, fontUnit), style.rotation);
|
|
9173
|
+
}
|
|
9174
|
+
function computeCachedTextWidth(context, text, font, rotation) {
|
|
9175
|
+
const size = computeCachedTextDimension(context, text, font);
|
|
9176
|
+
if (!rotation) return size.width;
|
|
9177
|
+
const cos = Math.abs(Math.cos(rotation));
|
|
9178
|
+
const sin = Math.abs(Math.sin(rotation));
|
|
9179
|
+
return size.width * cos + size.height * sin;
|
|
9180
|
+
}
|
|
9181
|
+
const textDimensionsCache = {};
|
|
9182
|
+
function computeTextDimension(context, text, style, fontUnit = "pt") {
|
|
9183
|
+
const size = computeCachedTextDimension(context, text, computeTextFont(style, fontUnit));
|
|
9184
|
+
if (!style.rotation) return size;
|
|
9185
|
+
const cos = Math.abs(Math.cos(style.rotation));
|
|
9186
|
+
const sin = Math.abs(Math.sin(style.rotation));
|
|
9187
|
+
return {
|
|
9188
|
+
width: size.width * cos + size.height * sin,
|
|
9189
|
+
height: size.height * cos + size.width * sin
|
|
9190
|
+
};
|
|
9191
|
+
}
|
|
9192
|
+
function computeCachedTextDimension(context, text, font) {
|
|
9193
|
+
if (!textDimensionsCache[font]) textDimensionsCache[font] = {};
|
|
9194
|
+
if (textDimensionsCache[font][text] === void 0) {
|
|
9195
|
+
context.save();
|
|
9196
|
+
context.font = font;
|
|
9197
|
+
const measure = context.measureText(text);
|
|
9198
|
+
context.restore();
|
|
9199
|
+
const width = measure.width;
|
|
9200
|
+
const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9201
|
+
textDimensionsCache[font][text] = {
|
|
9202
|
+
width,
|
|
9203
|
+
height
|
|
9204
|
+
};
|
|
9205
|
+
}
|
|
9206
|
+
return textDimensionsCache[font][text];
|
|
9207
|
+
}
|
|
9208
|
+
function fontSizeInPixels(fontSize) {
|
|
9209
|
+
return Math.round(fontSize * 96 / 72);
|
|
9210
|
+
}
|
|
9211
|
+
function computeTextFont(style, fontUnit = "pt") {
|
|
9212
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9213
|
+
}
|
|
9214
|
+
function computeTextFontSizeInPixels(style) {
|
|
9215
|
+
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
9216
|
+
}
|
|
9217
|
+
function splitWordToSpecificWidth(ctx, word, width, style) {
|
|
9218
|
+
if (computeTextWidth(ctx, word, style) <= width) return [word];
|
|
9219
|
+
const splitWord = [];
|
|
9220
|
+
let wordPart = "";
|
|
9221
|
+
for (const l of word) if (computeTextWidth(ctx, wordPart + l, style) > width) {
|
|
9222
|
+
splitWord.push(wordPart);
|
|
9223
|
+
wordPart = l;
|
|
9224
|
+
} else wordPart += l;
|
|
9225
|
+
splitWord.push(wordPart);
|
|
9226
|
+
return splitWord;
|
|
9227
|
+
}
|
|
9228
|
+
/**
|
|
9229
|
+
* Return the given text, split in multiple lines if needed. The text will be split in multiple
|
|
9230
|
+
* line if it contains NEWLINE characters, or if it's longer than the given width.
|
|
9231
|
+
*/
|
|
9232
|
+
function splitTextToWidth(ctx, text, style, width) {
|
|
9233
|
+
if (!style) style = {};
|
|
9234
|
+
if (isMarkdownLink(text)) text = parseMarkdownLink(text).label;
|
|
9235
|
+
const brokenText = [];
|
|
9236
|
+
const lines = text.includes("\n") ? text.split("\n") : [text];
|
|
9237
|
+
for (const line of lines) {
|
|
9238
|
+
const words = line.includes(" ") ? line.split(" ") : [line];
|
|
9239
|
+
if (!width) {
|
|
9240
|
+
brokenText.push(line);
|
|
9241
|
+
continue;
|
|
9242
|
+
}
|
|
9243
|
+
let textLine = "";
|
|
9244
|
+
let availableWidth = width;
|
|
9245
|
+
for (const word of words) {
|
|
9246
|
+
const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
|
|
9247
|
+
const lastPart = splitWord.pop();
|
|
9248
|
+
const lastPartWidth = computeTextWidth(ctx, lastPart, style);
|
|
9249
|
+
if (splitWord.length) {
|
|
9250
|
+
if (textLine !== "") {
|
|
9251
|
+
brokenText.push(textLine);
|
|
9252
|
+
textLine = "";
|
|
9253
|
+
availableWidth = width;
|
|
9254
|
+
}
|
|
9255
|
+
splitWord.forEach((wordPart) => {
|
|
9256
|
+
brokenText.push(wordPart);
|
|
9257
|
+
});
|
|
9258
|
+
textLine = lastPart;
|
|
9259
|
+
availableWidth = width - lastPartWidth;
|
|
9260
|
+
} else {
|
|
9261
|
+
const _word = textLine === "" ? lastPart : " " + lastPart;
|
|
9262
|
+
const wordWidth = computeTextWidth(ctx, _word, style);
|
|
9263
|
+
if (wordWidth <= availableWidth) {
|
|
9264
|
+
textLine += _word;
|
|
9265
|
+
availableWidth -= wordWidth;
|
|
9266
|
+
} else {
|
|
9267
|
+
brokenText.push(textLine);
|
|
9268
|
+
textLine = lastPart;
|
|
9269
|
+
availableWidth = width - lastPartWidth;
|
|
9270
|
+
}
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
if (textLine !== "") brokenText.push(textLine);
|
|
9274
|
+
}
|
|
9275
|
+
return brokenText;
|
|
9276
|
+
}
|
|
9277
|
+
/**
|
|
9278
|
+
* Return the font size that makes the width of a text match the given line width.
|
|
9279
|
+
* Minimum font size is 1.
|
|
9280
|
+
*
|
|
9281
|
+
* @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
|
|
9282
|
+
*/
|
|
9283
|
+
function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = .25) {
|
|
9284
|
+
let minFontSize = 1;
|
|
9285
|
+
if (getTextWidth(minFontSize) > lineWidth) return minFontSize;
|
|
9286
|
+
if (getTextWidth(maxFontSize) < lineWidth) return maxFontSize;
|
|
9287
|
+
let fontSize = (minFontSize + maxFontSize) / 2;
|
|
9288
|
+
let currentTextWidth = getTextWidth(fontSize);
|
|
9289
|
+
let iterations = 0;
|
|
9290
|
+
while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
|
|
9291
|
+
if (currentTextWidth >= lineWidth) maxFontSize = (minFontSize + maxFontSize) / 2;
|
|
9292
|
+
else minFontSize = (minFontSize + maxFontSize) / 2;
|
|
9293
|
+
fontSize = (minFontSize + maxFontSize) / 2;
|
|
9294
|
+
currentTextWidth = getTextWidth(fontSize);
|
|
9295
|
+
iterations++;
|
|
9296
|
+
}
|
|
9297
|
+
return fontSize;
|
|
9298
|
+
}
|
|
9299
|
+
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
|
|
9300
|
+
function toTrimmedLowerCase(str) {
|
|
9301
|
+
return str ? str.toLowerCase().trim() : "";
|
|
9302
|
+
}
|
|
9303
|
+
/**
|
|
9304
|
+
* Extract the fontSize from a context font string
|
|
9305
|
+
* @param font The (context) font string to parse
|
|
9306
|
+
* @returns The fontSize in pixels
|
|
9307
|
+
*/
|
|
9308
|
+
const pxRegex = /([0-9\.]*)px/;
|
|
9309
|
+
function getContextFontSize(font) {
|
|
9310
|
+
return Number(font.match(pxRegex)?.[1]);
|
|
9311
|
+
}
|
|
9312
|
+
function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
9313
|
+
let width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9314
|
+
if (width <= maxWidth) return text;
|
|
9315
|
+
const ellipsis = "…";
|
|
9316
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
|
|
9317
|
+
if (width <= ellipsisWidth) return text;
|
|
9318
|
+
let len = text.length;
|
|
9319
|
+
while (width >= maxWidth - ellipsisWidth && len-- > 0) {
|
|
9320
|
+
text = text.substring(0, len);
|
|
9321
|
+
width = computeCachedTextWidth(ctx, text, ctx.font);
|
|
9322
|
+
}
|
|
9323
|
+
return text + ellipsis;
|
|
9324
|
+
}
|
|
9325
|
+
function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10, highlightText = false) {
|
|
9326
|
+
if (highlightText) {
|
|
9327
|
+
context.save();
|
|
9328
|
+
context.fillStyle = lightenColor(context.fillStyle, DEFAULT_TEXT_HIGHLIGHT_PERCENT);
|
|
9329
|
+
}
|
|
9330
|
+
context.fillText(text, position.x, position.y);
|
|
9331
|
+
if (!underline && !strikethrough) return;
|
|
9332
|
+
const measure = context.measureText(text);
|
|
9333
|
+
const textWidth = measure.width;
|
|
9334
|
+
const textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
9335
|
+
const boxHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
|
|
9336
|
+
let { x, y } = position;
|
|
9337
|
+
let strikeY = y, underlineY = y;
|
|
9338
|
+
switch (context.textAlign) {
|
|
9339
|
+
case "center":
|
|
9340
|
+
x -= textWidth / 2;
|
|
9341
|
+
break;
|
|
9342
|
+
case "right":
|
|
9343
|
+
x -= textWidth;
|
|
9344
|
+
break;
|
|
9345
|
+
}
|
|
9346
|
+
switch (context.textBaseline) {
|
|
9347
|
+
case "top":
|
|
9348
|
+
underlineY += boxHeight - 2 * strokeWidth;
|
|
9349
|
+
strikeY += boxHeight / 2 - strokeWidth;
|
|
9350
|
+
break;
|
|
9351
|
+
case "middle":
|
|
9352
|
+
underlineY += boxHeight / 2 - strokeWidth;
|
|
9353
|
+
break;
|
|
9354
|
+
case "alphabetic":
|
|
9355
|
+
underlineY += 2 * strokeWidth;
|
|
9356
|
+
strikeY -= 3 * strokeWidth;
|
|
9357
|
+
break;
|
|
9358
|
+
case "bottom":
|
|
9359
|
+
underlineY = y;
|
|
9360
|
+
strikeY -= textHeight / 2 - strokeWidth / 2;
|
|
9361
|
+
break;
|
|
9362
|
+
}
|
|
9363
|
+
if (underline) {
|
|
9364
|
+
context.lineWidth = strokeWidth;
|
|
9365
|
+
context.strokeStyle = context.fillStyle;
|
|
9366
|
+
context.beginPath();
|
|
9367
|
+
context.moveTo(x, underlineY);
|
|
9368
|
+
context.lineTo(x + textWidth, underlineY);
|
|
9369
|
+
context.stroke();
|
|
9537
9370
|
}
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9371
|
+
if (strikethrough) {
|
|
9372
|
+
context.lineWidth = strokeWidth;
|
|
9373
|
+
context.strokeStyle = context.fillStyle;
|
|
9374
|
+
context.beginPath();
|
|
9375
|
+
context.moveTo(x, strikeY);
|
|
9376
|
+
context.lineTo(x + textWidth, strikeY);
|
|
9377
|
+
context.stroke();
|
|
9541
9378
|
}
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
"#73c476",
|
|
9562
|
-
"#00441b"
|
|
9563
|
-
],
|
|
9564
|
-
oranges: [
|
|
9565
|
-
"#fff5eb",
|
|
9566
|
-
"#fd8c3b",
|
|
9567
|
-
"#7f2704"
|
|
9568
|
-
],
|
|
9569
|
-
purples: [
|
|
9570
|
-
"#fcfbfd",
|
|
9571
|
-
"#9e9ac8",
|
|
9572
|
-
"#3f007d"
|
|
9573
|
-
],
|
|
9574
|
-
viridis: [
|
|
9575
|
-
"#440154",
|
|
9576
|
-
"#21918c",
|
|
9577
|
-
"#fde725"
|
|
9578
|
-
],
|
|
9579
|
-
cividis: [
|
|
9580
|
-
"#00224e",
|
|
9581
|
-
"#7d7c78",
|
|
9582
|
-
"#fee838"
|
|
9583
|
-
],
|
|
9584
|
-
rainbow: [
|
|
9585
|
-
"#B41DB4",
|
|
9586
|
-
"#FFFF00",
|
|
9587
|
-
"#00FFFF"
|
|
9588
|
-
]
|
|
9589
|
-
};
|
|
9590
|
-
const COLORSCALES = Object.keys(COLORSCHEMES);
|
|
9379
|
+
if (highlightText) context.restore();
|
|
9380
|
+
}
|
|
9381
|
+
function sliceTextToFitWidth(context, width, text, style, fontUnit = "pt") {
|
|
9382
|
+
if (computeTextWidth(context, text, style, fontUnit) <= width) return text;
|
|
9383
|
+
const ellipsis = "...";
|
|
9384
|
+
const ellipsisWidth = computeTextWidth(context, ellipsis, style, fontUnit);
|
|
9385
|
+
if (ellipsisWidth >= width) return "";
|
|
9386
|
+
let lowerBoundLen = 1;
|
|
9387
|
+
let upperBoundLen = text.length;
|
|
9388
|
+
let currentWidth;
|
|
9389
|
+
while (lowerBoundLen <= upperBoundLen) {
|
|
9390
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
9391
|
+
currentWidth = computeTextWidth(context, text.slice(0, currentLen), style, fontUnit);
|
|
9392
|
+
if (currentWidth + ellipsisWidth > width) upperBoundLen = currentLen - 1;
|
|
9393
|
+
else lowerBoundLen = currentLen + 1;
|
|
9394
|
+
}
|
|
9395
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
9396
|
+
return slicedText ? slicedText + ellipsis : "";
|
|
9397
|
+
}
|
|
9591
9398
|
/**
|
|
9592
|
-
*
|
|
9593
|
-
*
|
|
9399
|
+
* Return the position to draw text on a rotated canvas to ensure that the rotated text alignment correspond
|
|
9400
|
+
* with to original's text vertical and horizontal alignment.
|
|
9594
9401
|
*/
|
|
9595
|
-
function
|
|
9596
|
-
if (
|
|
9597
|
-
|
|
9598
|
-
const
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9402
|
+
function computeRotationPosition(rect, style) {
|
|
9403
|
+
if (!style.rotation || style.rotation % (Math.PI * 2) === 0) return rect;
|
|
9404
|
+
let { x, y } = rect;
|
|
9405
|
+
const cos = Math.cos(-style.rotation);
|
|
9406
|
+
const sin = Math.sin(-style.rotation);
|
|
9407
|
+
const width = rect.textWidth - 2 * 4;
|
|
9408
|
+
const height = rect.textHeight;
|
|
9409
|
+
const center = style.align === "center";
|
|
9410
|
+
const rotateTowardCellCenter = style.align === "left" === sin < 0;
|
|
9411
|
+
const sh = sin * height;
|
|
9412
|
+
const sw = Math.abs(sin * width);
|
|
9413
|
+
const ch = cos * height;
|
|
9414
|
+
if (style.verticalAlign === "top") if (center) {
|
|
9415
|
+
y += sw / 2;
|
|
9416
|
+
x -= sh / 2;
|
|
9417
|
+
} else if (rotateTowardCellCenter) x -= sh;
|
|
9418
|
+
else y += sw;
|
|
9419
|
+
else if (!style.verticalAlign || style.verticalAlign === "bottom") {
|
|
9420
|
+
y += height - ch;
|
|
9421
|
+
if (center) {
|
|
9422
|
+
y -= sw / 2;
|
|
9423
|
+
x -= sh / 2;
|
|
9424
|
+
} else if (rotateTowardCellCenter) {
|
|
9425
|
+
x -= sh;
|
|
9426
|
+
y -= sw;
|
|
9427
|
+
}
|
|
9428
|
+
} else if (center) {
|
|
9429
|
+
x -= sh / 2;
|
|
9430
|
+
y -= height / 2;
|
|
9431
|
+
if (rotateTowardCellCenter) y += sh;
|
|
9432
|
+
else y -= sh;
|
|
9433
|
+
} else if (rotateTowardCellCenter) {
|
|
9434
|
+
x -= sh;
|
|
9435
|
+
y -= sw / 2;
|
|
9436
|
+
} else y += sw / 2 + ch / 4;
|
|
9437
|
+
return {
|
|
9438
|
+
x: cos * x - sin * y,
|
|
9439
|
+
y: cos * y + sin * x
|
|
9440
|
+
};
|
|
9441
|
+
}
|
|
9442
|
+
|
|
9443
|
+
//#endregion
|
|
9444
|
+
//#region src/components/figures/chart/chartJs/chartjs_colorscale_plugin.ts
|
|
9445
|
+
/** This is a chartJS plugin that will draw the heatmap colorscale at the chart legend position */
|
|
9446
|
+
const chartColorScalePlugin = {
|
|
9447
|
+
id: "chartColorScalePlugin",
|
|
9448
|
+
afterDatasetsDraw(chart, args, options) {
|
|
9449
|
+
if (!options.position || options.position === "none" || !options.colorScale.length) return;
|
|
9450
|
+
const ctx = chart.ctx;
|
|
9451
|
+
ctx.save();
|
|
9452
|
+
ctx.textAlign = "center";
|
|
9453
|
+
ctx.textBaseline = "middle";
|
|
9454
|
+
ctx.miterLimit = 1;
|
|
9455
|
+
const gradientHeight = (chart.chartArea.bottom - chart.chartArea.top) / 2;
|
|
9456
|
+
const gradientWidth = 10;
|
|
9457
|
+
const gradientX = options.position === "left" ? 20 : ctx.canvas.width - 70;
|
|
9458
|
+
const gradientY = chart.chartArea.top;
|
|
9459
|
+
const gradient = ctx.createLinearGradient(0, gradientY + gradientHeight, 0, gradientY);
|
|
9460
|
+
const step = 1 / (options.colorScale.length - 1);
|
|
9461
|
+
options.colorScale.forEach((color, index) => {
|
|
9462
|
+
gradient.addColorStop(index * step, color);
|
|
9612
9463
|
});
|
|
9464
|
+
ctx.fillStyle = gradient;
|
|
9465
|
+
ctx.fillRect(gradientX, gradientY, gradientWidth, gradientHeight);
|
|
9466
|
+
ctx.fillStyle = options.fontColor ?? "black";
|
|
9467
|
+
ctx.font = getDefaultContextFont(12);
|
|
9468
|
+
ctx.textAlign = "left";
|
|
9469
|
+
let minValue = Math.round(options.minValue * 100) / 100;
|
|
9470
|
+
let maxValue = Math.round(options.maxValue * 100) / 100;
|
|
9471
|
+
if (options.minValue === options.maxValue) {
|
|
9472
|
+
minValue -= 1;
|
|
9473
|
+
maxValue += 1;
|
|
9474
|
+
}
|
|
9475
|
+
const formattedMaxValue = humanizeNumber({
|
|
9476
|
+
value: maxValue,
|
|
9477
|
+
format: void 0
|
|
9478
|
+
}, options.locale);
|
|
9479
|
+
const formattedMinValue = humanizeNumber({
|
|
9480
|
+
value: minValue,
|
|
9481
|
+
format: void 0
|
|
9482
|
+
}, options.locale);
|
|
9483
|
+
ctx.fillText(formattedMinValue, gradientX + gradientWidth + 5, gradientY + gradientHeight - 6);
|
|
9484
|
+
ctx.fillText(formattedMaxValue, gradientX + gradientWidth + 5, gradientY + 6);
|
|
9485
|
+
ctx.restore();
|
|
9613
9486
|
}
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9487
|
+
};
|
|
9488
|
+
|
|
9489
|
+
//#endregion
|
|
9490
|
+
//#region src/components/figures/chart/chartJs/chartjs_funnel_chart.ts
|
|
9491
|
+
function getFunnelChartController() {
|
|
9492
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9493
|
+
return class FunnelChartController extends globalThis.Chart.BarController {
|
|
9494
|
+
static id = "funnel";
|
|
9495
|
+
static defaults = {
|
|
9496
|
+
...globalThis.Chart?.BarController.defaults,
|
|
9497
|
+
dataElementType: "funnel",
|
|
9498
|
+
animation: { duration: (ctx) => {
|
|
9499
|
+
if (ctx.type !== "data") return 1e3;
|
|
9500
|
+
return 1e3 * (ctx.raw[1] / Math.max(...ctx.dataset.data.map((data) => data[1])));
|
|
9501
|
+
} }
|
|
9502
|
+
};
|
|
9503
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9504
|
+
updateElements(rects, start, count, mode) {
|
|
9505
|
+
super.updateElements(rects, start, count, mode);
|
|
9506
|
+
for (let i = start; i < start + count; i++) {
|
|
9507
|
+
const rect = rects[i];
|
|
9508
|
+
this.updateElement(rect, i, { nextElement: rects[i + 1] }, mode);
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9618
9511
|
};
|
|
9619
9512
|
}
|
|
9620
|
-
function
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9513
|
+
function getFunnelChartElement() {
|
|
9514
|
+
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
9515
|
+
/**
|
|
9516
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9517
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9518
|
+
*/
|
|
9519
|
+
return class FunnelChartElement extends globalThis.Chart.BarElement {
|
|
9520
|
+
static id = "funnel";
|
|
9521
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9522
|
+
draw(ctx) {
|
|
9523
|
+
ctx.save();
|
|
9524
|
+
const { x, y, height, nextElement, base, options } = this.getProps([
|
|
9525
|
+
"x",
|
|
9526
|
+
"y",
|
|
9527
|
+
"width",
|
|
9528
|
+
"height",
|
|
9529
|
+
"nextElement",
|
|
9530
|
+
"base",
|
|
9531
|
+
"options"
|
|
9532
|
+
]);
|
|
9533
|
+
const width = getElementWidth(this);
|
|
9534
|
+
const offset = (width - (nextElement ? getElementWidth(nextElement) : 0)) / 2;
|
|
9535
|
+
const startX = Math.min(x, base);
|
|
9536
|
+
const startY = y - height / 2;
|
|
9537
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9538
|
+
ctx.beginPath();
|
|
9539
|
+
ctx.moveTo(startX, startY);
|
|
9540
|
+
ctx.lineTo(startX + width, startY);
|
|
9541
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9542
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9543
|
+
ctx.closePath();
|
|
9544
|
+
ctx.fill();
|
|
9545
|
+
if (options.borderWidth) {
|
|
9546
|
+
ctx.strokeStyle = options.borderColor;
|
|
9547
|
+
ctx.lineWidth = options.borderWidth;
|
|
9548
|
+
ctx.stroke();
|
|
9549
|
+
}
|
|
9550
|
+
ctx.restore();
|
|
9551
|
+
}
|
|
9552
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9553
|
+
inRange(mouseX, mouseY) {
|
|
9554
|
+
const { x, y, height, nextElement, base } = this.getProps([
|
|
9555
|
+
"x",
|
|
9556
|
+
"y",
|
|
9557
|
+
"width",
|
|
9558
|
+
"height",
|
|
9559
|
+
"nextElement",
|
|
9560
|
+
"base",
|
|
9561
|
+
"options"
|
|
9562
|
+
]);
|
|
9563
|
+
const width = getElementWidth(this);
|
|
9564
|
+
const nextElementWidth = nextElement ? getElementWidth(nextElement) : 0;
|
|
9565
|
+
const startX = Math.min(x, base);
|
|
9566
|
+
const startY = y - height / 2;
|
|
9567
|
+
if (mouseY < startY || mouseY > startY + height) return false;
|
|
9568
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9569
|
+
const left = startX + offset * (mouseY - startY) / height;
|
|
9570
|
+
const right = startX + width - offset * (mouseY - startY) / height;
|
|
9571
|
+
if (mouseX < left || mouseX > right) return false;
|
|
9572
|
+
return true;
|
|
9573
|
+
}
|
|
9574
|
+
};
|
|
9630
9575
|
}
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9576
|
+
/**
|
|
9577
|
+
* Get an element width.
|
|
9578
|
+
*
|
|
9579
|
+
* The property width is undefined during animations, we need to compute it manually.
|
|
9580
|
+
*/
|
|
9581
|
+
function getElementWidth(element) {
|
|
9582
|
+
const { x, base } = element.getProps(["x", "base"]);
|
|
9583
|
+
return Math.max(x, base) - Math.min(x, base);
|
|
9637
9584
|
}
|
|
9585
|
+
/**
|
|
9586
|
+
* Position the tooltip inside the trapezoid.
|
|
9587
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9588
|
+
*/
|
|
9589
|
+
const funnelTooltipPositioner = function(elements) {
|
|
9590
|
+
if (!elements.length) return {
|
|
9591
|
+
x: 0,
|
|
9592
|
+
y: 0
|
|
9593
|
+
};
|
|
9594
|
+
const { x, y, base, width, height } = elements[0].element.getProps([
|
|
9595
|
+
"x",
|
|
9596
|
+
"y",
|
|
9597
|
+
"width",
|
|
9598
|
+
"height",
|
|
9599
|
+
"base"
|
|
9600
|
+
]);
|
|
9601
|
+
const startX = Math.min(x, base);
|
|
9602
|
+
const startY = y - height / 2;
|
|
9603
|
+
return {
|
|
9604
|
+
x: startX + width * 2 / 3,
|
|
9605
|
+
y: startY + height / 2
|
|
9606
|
+
};
|
|
9607
|
+
};
|
|
9608
|
+
|
|
9609
|
+
//#endregion
|
|
9610
|
+
//#region src/components/figures/chart/chartJs/chartjs_minor_grid_plugin.ts
|
|
9611
|
+
const chartMinorGridPlugin = {
|
|
9612
|
+
id: "o-spreadsheet-minor-gridlines",
|
|
9613
|
+
beforeDatasetsDraw(chart) {
|
|
9614
|
+
const ctx = chart.ctx;
|
|
9615
|
+
const chartArea = chart.chartArea;
|
|
9616
|
+
if (!chartArea) return;
|
|
9617
|
+
for (const scaleId in chart.scales) {
|
|
9618
|
+
const scale = chart.scales[scaleId];
|
|
9619
|
+
const options = scale.options;
|
|
9620
|
+
const minor = options?.grid?.minor;
|
|
9621
|
+
if (!minor?.display) continue;
|
|
9622
|
+
const showMajorGrid = options?.grid?.display;
|
|
9623
|
+
const ticks = scale.ticks;
|
|
9624
|
+
if (!ticks || ticks.length < 2) continue;
|
|
9625
|
+
ctx.save();
|
|
9626
|
+
ctx.lineWidth = 1;
|
|
9627
|
+
ctx.strokeStyle = minor.color ?? options?.grid?.color ?? "#e6e6e6";
|
|
9628
|
+
for (let i = 0; i < ticks.length - 1; i++) {
|
|
9629
|
+
const start = scale.getPixelForTick(i);
|
|
9630
|
+
const end = scale.getPixelForTick(i + 1);
|
|
9631
|
+
if (!isFinite(start) || !isFinite(end)) continue;
|
|
9632
|
+
for (let j = showMajorGrid ? 1 : 0; j < 4; j++) {
|
|
9633
|
+
const ratio = j / 4;
|
|
9634
|
+
const position = Math.round(start + (end - start) * ratio) + .5;
|
|
9635
|
+
ctx.beginPath();
|
|
9636
|
+
if (scale.isHorizontal()) {
|
|
9637
|
+
ctx.moveTo(position, chartArea.top);
|
|
9638
|
+
ctx.lineTo(position, chartArea.bottom);
|
|
9639
|
+
} else {
|
|
9640
|
+
ctx.moveTo(chartArea.left, position);
|
|
9641
|
+
ctx.lineTo(chartArea.right, position);
|
|
9642
|
+
}
|
|
9643
|
+
ctx.stroke();
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
ctx.restore();
|
|
9647
|
+
}
|
|
9648
|
+
}
|
|
9649
|
+
};
|
|
9638
9650
|
|
|
9639
9651
|
//#endregion
|
|
9640
9652
|
//#region src/xlsx/constants.ts
|
|
@@ -10128,6 +10140,7 @@ set(value) {
|
|
|
10128
10140
|
//#endregion
|
|
10129
10141
|
//#region src/components/figures/chart/chartJs/chartjs_show_values_plugin.ts
|
|
10130
10142
|
const MINIMAL_VERTICAL_DISTANCE = 13;
|
|
10143
|
+
const HORIZONTAL_PADDING = 3;
|
|
10131
10144
|
function isLineOverlayOnBarChart(options, dataset) {
|
|
10132
10145
|
return options.type === "bar" && dataset.type === "line";
|
|
10133
10146
|
}
|
|
@@ -10137,175 +10150,276 @@ set(value) {
|
|
|
10137
10150
|
afterDatasetsDraw(chart, args, options) {
|
|
10138
10151
|
if (!options.showValues) return;
|
|
10139
10152
|
if (!chart._metasets?.[0]?.data) return;
|
|
10140
|
-
const ctx = chart.ctx;
|
|
10141
|
-
ctx.save();
|
|
10142
|
-
ctx.textAlign = "center";
|
|
10143
|
-
ctx.textBaseline = "middle";
|
|
10144
|
-
ctx.miterLimit = 1;
|
|
10145
10153
|
switch (options.type) {
|
|
10146
10154
|
case "pie":
|
|
10147
|
-
|
|
10155
|
+
drawPieValues(chart, options);
|
|
10148
10156
|
break;
|
|
10149
10157
|
case "line":
|
|
10150
10158
|
case "scatter":
|
|
10159
|
+
drawLineValues(chart, options);
|
|
10160
|
+
break;
|
|
10151
10161
|
case "combo":
|
|
10152
10162
|
case "waterfall":
|
|
10163
|
+
drawComboValues(chart, options);
|
|
10164
|
+
break;
|
|
10153
10165
|
case "radar":
|
|
10154
|
-
|
|
10166
|
+
drawRadarValues(chart, options);
|
|
10155
10167
|
break;
|
|
10156
10168
|
case "bar":
|
|
10157
|
-
options.horizontal ?
|
|
10169
|
+
options.horizontal ? drawHorizontalBarValues(chart, options) : drawVerticalBarValues(chart, options);
|
|
10158
10170
|
break;
|
|
10159
10171
|
case "pyramid":
|
|
10160
|
-
|
|
10172
|
+
drawHorizontalBarValues(chart, options);
|
|
10161
10173
|
break;
|
|
10162
10174
|
case "calendar":
|
|
10163
|
-
|
|
10175
|
+
drawCalendarValues(chart, options);
|
|
10164
10176
|
break;
|
|
10165
10177
|
case "bubble":
|
|
10166
|
-
|
|
10178
|
+
drawBubbleValues(chart, options);
|
|
10167
10179
|
break;
|
|
10168
10180
|
case "funnel":
|
|
10169
|
-
|
|
10181
|
+
drawFunnelValues(chart, options);
|
|
10170
10182
|
break;
|
|
10171
10183
|
}
|
|
10172
|
-
ctx.restore();
|
|
10173
10184
|
}
|
|
10174
10185
|
};
|
|
10175
|
-
function
|
|
10176
|
-
|
|
10177
|
-
ctx
|
|
10178
|
-
ctx.
|
|
10179
|
-
ctx.
|
|
10180
|
-
|
|
10181
|
-
|
|
10186
|
+
function drawValues(args) {
|
|
10187
|
+
const { chart, options, getNumberValue, direction } = args;
|
|
10188
|
+
const ctx = chart.ctx;
|
|
10189
|
+
ctx.save();
|
|
10190
|
+
ctx.textAlign = "center";
|
|
10191
|
+
ctx.textBaseline = "middle";
|
|
10192
|
+
ctx.miterLimit = 1;
|
|
10182
10193
|
const textsPositions = {};
|
|
10183
10194
|
for (const dataset of chart._metasets) {
|
|
10184
|
-
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden
|
|
10185
|
-
const yAxisScale = chart.scales[dataset.yAxisID];
|
|
10195
|
+
if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) continue;
|
|
10186
10196
|
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
10187
|
-
const
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
const
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10197
|
+
const numberValue = getNumberValue(dataset, i);
|
|
10198
|
+
if (numberValue === void 0 || isNaN(numberValue)) continue;
|
|
10199
|
+
const chartElement = dataset.data[i];
|
|
10200
|
+
const elementColor = chartElement.options.backgroundColor;
|
|
10201
|
+
if (!elementColor || colorToRGBA(elementColor).a !== 1) continue;
|
|
10202
|
+
const valueToDisplay = options.callback(numberValue, dataset, i);
|
|
10203
|
+
const textSize = getTextDimensions(valueToDisplay, ctx);
|
|
10204
|
+
const callbackArgs = {
|
|
10205
|
+
dataset,
|
|
10206
|
+
chartElement,
|
|
10207
|
+
numberValue,
|
|
10208
|
+
valueIndex: i,
|
|
10209
|
+
textSize,
|
|
10210
|
+
options,
|
|
10211
|
+
chart
|
|
10212
|
+
};
|
|
10213
|
+
const position = args.getValuePosition(callbackArgs);
|
|
10214
|
+
if (args.shouldSkipValue?.(callbackArgs)) continue;
|
|
10215
|
+
if (direction === "vertical") {
|
|
10216
|
+
const key = Math.round(position.x);
|
|
10217
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10218
|
+
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);
|
|
10219
|
+
textsPositions[key].push(position.y);
|
|
10220
|
+
} else {
|
|
10221
|
+
const key = Math.round(position.y);
|
|
10222
|
+
if (!textsPositions[key]) textsPositions[key] = [];
|
|
10223
|
+
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;
|
|
10224
|
+
textsPositions[key].push(position.x);
|
|
10225
|
+
}
|
|
10226
|
+
const { strokeColor, textColor } = args.getTextColors(callbackArgs);
|
|
10227
|
+
if (!!strokeColor) {
|
|
10228
|
+
ctx.strokeStyle = strokeColor;
|
|
10229
|
+
ctx.lineWidth = 3;
|
|
10230
|
+
ctx.strokeText(valueToDisplay, position.x, position.y);
|
|
10231
|
+
}
|
|
10232
|
+
ctx.fillStyle = textColor;
|
|
10233
|
+
ctx.lineWidth = 1;
|
|
10234
|
+
ctx.fillText(valueToDisplay, position.x, position.y);
|
|
10208
10235
|
}
|
|
10209
10236
|
}
|
|
10237
|
+
ctx.restore();
|
|
10210
10238
|
}
|
|
10211
|
-
function
|
|
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
|
-
|
|
10273
|
-
|
|
10239
|
+
function drawVerticalBarValues(chart, options) {
|
|
10240
|
+
drawValues({
|
|
10241
|
+
chart,
|
|
10242
|
+
options,
|
|
10243
|
+
direction: "vertical",
|
|
10244
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10245
|
+
getValuePosition: getVerticalBarValuePosition,
|
|
10246
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10247
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10248
|
+
});
|
|
10249
|
+
}
|
|
10250
|
+
function drawRadarValues(chart, options) {
|
|
10251
|
+
drawValues({
|
|
10252
|
+
chart,
|
|
10253
|
+
options,
|
|
10254
|
+
direction: "vertical",
|
|
10255
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].r),
|
|
10256
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10257
|
+
x: chartElement.x,
|
|
10258
|
+
y: chartElement.y - 10
|
|
10259
|
+
}),
|
|
10260
|
+
shouldSkipValue: () => false,
|
|
10261
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10262
|
+
});
|
|
10263
|
+
}
|
|
10264
|
+
function drawLineValues(chart, options) {
|
|
10265
|
+
drawValues({
|
|
10266
|
+
chart,
|
|
10267
|
+
options,
|
|
10268
|
+
direction: "vertical",
|
|
10269
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10270
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10271
|
+
x: chartElement.x,
|
|
10272
|
+
y: chartElement.y - 10
|
|
10273
|
+
}),
|
|
10274
|
+
shouldSkipValue: () => false,
|
|
10275
|
+
getTextColors: chartElementColoredTextWithChartBackgroundHalo
|
|
10276
|
+
});
|
|
10277
|
+
}
|
|
10278
|
+
function drawComboValues(chart, options) {
|
|
10279
|
+
drawValues({
|
|
10280
|
+
chart,
|
|
10281
|
+
options,
|
|
10282
|
+
direction: "vertical",
|
|
10283
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i].y),
|
|
10284
|
+
getValuePosition: (args) => args.dataset.type === "line" ? {
|
|
10285
|
+
x: args.chartElement.x,
|
|
10286
|
+
y: args.chartElement.y - 10
|
|
10287
|
+
} : getVerticalBarValuePosition(args),
|
|
10288
|
+
shouldSkipValue: () => false,
|
|
10289
|
+
getTextColors: (args) => args.dataset.type === "line" ? chartElementColoredTextWithChartBackgroundHalo(args) : chartBackgroundColoredTextWithElementColoredHalo(args)
|
|
10290
|
+
});
|
|
10291
|
+
}
|
|
10292
|
+
function drawCalendarValues(chart, options) {
|
|
10293
|
+
drawValues({
|
|
10294
|
+
chart,
|
|
10295
|
+
options,
|
|
10296
|
+
direction: "vertical",
|
|
10297
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10298
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10299
|
+
x: chartElement.x,
|
|
10300
|
+
y: chartElement.y + chartElement.height / 2
|
|
10301
|
+
}),
|
|
10302
|
+
shouldSkipValue: ({ chartElement, textSize }) => textSize.height + 2 > Math.abs(chartElement.height) - 2,
|
|
10303
|
+
getTextColors: ({ chartElement }) => ({
|
|
10304
|
+
strokeColor: void 0,
|
|
10305
|
+
textColor: chartFontColor(chartElement.options.backgroundColor)
|
|
10306
|
+
})
|
|
10307
|
+
});
|
|
10308
|
+
}
|
|
10309
|
+
function drawBubbleValues(chart, options) {
|
|
10310
|
+
const canDrawTextInsideBubble = (chartElement, textSize) => {
|
|
10311
|
+
const radius = chartElement.options.radius ?? globalThis.Chart?.defaults.elements.point.radius ?? 3;
|
|
10312
|
+
return textSize.height < radius * 2;
|
|
10313
|
+
};
|
|
10314
|
+
drawValues({
|
|
10315
|
+
chart,
|
|
10316
|
+
options,
|
|
10317
|
+
direction: "vertical",
|
|
10318
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].y,
|
|
10319
|
+
getValuePosition: ({ chartElement, textSize }) => ({
|
|
10320
|
+
x: chartElement.x,
|
|
10321
|
+
y: canDrawTextInsideBubble(chartElement, textSize) ? chartElement.y : chartElement.y - 10
|
|
10322
|
+
}),
|
|
10323
|
+
shouldSkipValue: () => false,
|
|
10324
|
+
getTextColors: (args) => {
|
|
10325
|
+
return canDrawTextInsideBubble(args.chartElement, args.textSize) ? chartBackgroundColoredTextWithElementColoredHalo(args) : chartElementColoredTextWithChartBackgroundHalo(args);
|
|
10326
|
+
}
|
|
10327
|
+
});
|
|
10328
|
+
}
|
|
10329
|
+
function drawHorizontalBarValues(chart, options) {
|
|
10330
|
+
drawValues({
|
|
10331
|
+
chart,
|
|
10332
|
+
options,
|
|
10333
|
+
direction: "horizontal",
|
|
10334
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10335
|
+
getValuePosition: ({ chartElement, numberValue, textSize, dataset }) => {
|
|
10336
|
+
const xZeroLine = chart.scales[dataset.xAxisID].getPixelForValue(0);
|
|
10337
|
+
const distanceFromAxisOrigin = Math.abs(chartElement.x - xZeroLine);
|
|
10274
10338
|
let xPosition;
|
|
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
|
-
|
|
10308
|
-
}
|
|
10339
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10340
|
+
if (distanceFromAxisOrigin < textSize.width) xPosition = xZeroLine + sign * (textSize.width / 2 + HORIZONTAL_PADDING);
|
|
10341
|
+
else xPosition = chartElement.x - sign * (chartElement.width / 2);
|
|
10342
|
+
return {
|
|
10343
|
+
x: xPosition,
|
|
10344
|
+
y: chartElement.y
|
|
10345
|
+
};
|
|
10346
|
+
},
|
|
10347
|
+
shouldSkipValue: ({ dataset }) => isLineOverlayOnBarChart(options, dataset),
|
|
10348
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10349
|
+
});
|
|
10350
|
+
}
|
|
10351
|
+
function drawFunnelValues(chart, options) {
|
|
10352
|
+
drawValues({
|
|
10353
|
+
chart,
|
|
10354
|
+
options,
|
|
10355
|
+
direction: "horizontal",
|
|
10356
|
+
getNumberValue: (dataset, i) => dataset._parsed[i].x,
|
|
10357
|
+
getValuePosition: ({ chartElement }) => ({
|
|
10358
|
+
x: chartElement.x - chartElement.width / 2,
|
|
10359
|
+
y: chartElement.y
|
|
10360
|
+
}),
|
|
10361
|
+
shouldSkipValue: () => false,
|
|
10362
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10363
|
+
});
|
|
10364
|
+
}
|
|
10365
|
+
function drawPieValues(chart, options) {
|
|
10366
|
+
const getSliceDimensions = (chartElement) => {
|
|
10367
|
+
const { startAngle, endAngle, innerRadius, outerRadius } = chartElement;
|
|
10368
|
+
return {
|
|
10369
|
+
midAngle: (startAngle + endAngle) / 2,
|
|
10370
|
+
midRadius: (innerRadius + outerRadius) / 2
|
|
10371
|
+
};
|
|
10372
|
+
};
|
|
10373
|
+
drawValues({
|
|
10374
|
+
chart,
|
|
10375
|
+
options,
|
|
10376
|
+
direction: "vertical",
|
|
10377
|
+
getNumberValue: (dataset, i) => Number(dataset._parsed[i]),
|
|
10378
|
+
getValuePosition: ({ chartElement }) => {
|
|
10379
|
+
const { midAngle, midRadius } = getSliceDimensions(chartElement);
|
|
10380
|
+
return {
|
|
10381
|
+
x: chartElement.x + midRadius * Math.cos(midAngle),
|
|
10382
|
+
y: chartElement.y + midRadius * Math.sin(midAngle)
|
|
10383
|
+
};
|
|
10384
|
+
},
|
|
10385
|
+
shouldSkipValue: ({ chartElement, textSize }) => {
|
|
10386
|
+
const { midRadius } = getSliceDimensions(chartElement);
|
|
10387
|
+
const sliceAngle = chartElement.endAngle - chartElement.startAngle;
|
|
10388
|
+
const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
|
|
10389
|
+
return sliceAngle < Math.PI / 2 && (textSize.width >= midWidth || midWidth < textSize.height);
|
|
10390
|
+
},
|
|
10391
|
+
getTextColors: chartBackgroundColoredTextWithElementColoredHalo
|
|
10392
|
+
});
|
|
10393
|
+
}
|
|
10394
|
+
function getTextDimensions(text, ctx) {
|
|
10395
|
+
return computeCachedTextDimension(ctx, text, computeTextFont({ fontSize: globalThis.Chart?.defaults.font.size ?? 12 }, "px"));
|
|
10396
|
+
}
|
|
10397
|
+
function chartBackgroundColoredTextWithElementColoredHalo(args) {
|
|
10398
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10399
|
+
return {
|
|
10400
|
+
strokeColor: chartElement.options.backgroundColor,
|
|
10401
|
+
textColor: options.background(numberValue, dataset, valueIndex) || "#ffffff"
|
|
10402
|
+
};
|
|
10403
|
+
}
|
|
10404
|
+
function chartElementColoredTextWithChartBackgroundHalo(args) {
|
|
10405
|
+
const { dataset, numberValue, valueIndex, chartElement, options } = args;
|
|
10406
|
+
return {
|
|
10407
|
+
strokeColor: options.background(numberValue, dataset, valueIndex) || "#ffffff",
|
|
10408
|
+
textColor: chartElement.options.backgroundColor
|
|
10409
|
+
};
|
|
10410
|
+
}
|
|
10411
|
+
function getVerticalBarValuePosition(args) {
|
|
10412
|
+
const { chartElement, numberValue, dataset, textSize, chart } = args;
|
|
10413
|
+
const yZeroLine = chart.scales[dataset.yAxisID].getPixelForValue(0);
|
|
10414
|
+
const distanceFromAxisOrigin = Math.abs(yZeroLine - chartElement.y);
|
|
10415
|
+
const sign = numberValue < 0 ? -1 : 1;
|
|
10416
|
+
let yPosition = 0;
|
|
10417
|
+
if (distanceFromAxisOrigin < textSize.height) yPosition = yZeroLine - sign * (textSize.height / 2);
|
|
10418
|
+
else yPosition = chartElement.y + sign * (chartElement.height / 2);
|
|
10419
|
+
return {
|
|
10420
|
+
x: chartElement.x,
|
|
10421
|
+
y: yPosition
|
|
10422
|
+
};
|
|
10309
10423
|
}
|
|
10310
10424
|
|
|
10311
10425
|
//#endregion
|
|
@@ -11162,6 +11276,7 @@ set(value) {
|
|
|
11162
11276
|
hidden,
|
|
11163
11277
|
borderColor,
|
|
11164
11278
|
backgroundColor: borderColor,
|
|
11279
|
+
pointBackgroundColor: borderColor,
|
|
11165
11280
|
pointRadius: definition.hideDataMarkers ? 0 : 3
|
|
11166
11281
|
};
|
|
11167
11282
|
if (fill) {
|
|
@@ -12074,11 +12189,11 @@ set(value) {
|
|
|
12074
12189
|
if (structure.baseline) {
|
|
12075
12190
|
ctx.font = structure.baseline.style.font;
|
|
12076
12191
|
ctx.fillStyle = structure.baseline.style.color;
|
|
12077
|
-
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough);
|
|
12192
|
+
drawDecoratedText(ctx, structure.baseline.text, structure.baseline.position, structure.baseline.style.underline, structure.baseline.style.strikethrough, void 0, structure.baseline.style.highlightText);
|
|
12078
12193
|
}
|
|
12079
12194
|
if (structure.baselineArrow && structure.baselineArrow.style.size > 0 && Path2DConstructor) {
|
|
12080
12195
|
ctx.save();
|
|
12081
|
-
ctx.fillStyle = structure.baselineArrow.style.color;
|
|
12196
|
+
ctx.fillStyle = structure.baselineArrow.style.highlight ? lightenColor(structure.baselineArrow.style.color, DEFAULT_TEXT_HIGHLIGHT_PERCENT) : structure.baselineArrow.style.color;
|
|
12082
12197
|
ctx.translate(structure.baselineArrow.position.x, structure.baselineArrow.position.y);
|
|
12083
12198
|
const ratio = structure.baselineArrow.style.size / 10;
|
|
12084
12199
|
ctx.scale(ratio, ratio);
|
|
@@ -12096,18 +12211,18 @@ set(value) {
|
|
|
12096
12211
|
const descr = structure.baselineDescr;
|
|
12097
12212
|
ctx.font = descr.style.font;
|
|
12098
12213
|
ctx.fillStyle = descr.style.color;
|
|
12099
|
-
ctx
|
|
12214
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.baseline?.style.highlightText);
|
|
12100
12215
|
}
|
|
12101
12216
|
if (structure.key) {
|
|
12102
12217
|
ctx.font = structure.key.style.font;
|
|
12103
12218
|
ctx.fillStyle = structure.key.style.color;
|
|
12104
|
-
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
|
|
12219
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough, void 0, structure.key.style.highlightText);
|
|
12105
12220
|
}
|
|
12106
12221
|
if (structure.keyDescr) {
|
|
12107
12222
|
const descr = structure.keyDescr;
|
|
12108
12223
|
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12109
12224
|
ctx.fillStyle = descr.style.color;
|
|
12110
|
-
ctx
|
|
12225
|
+
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position, void 0, void 0, void 0, structure.key?.style.highlightText);
|
|
12111
12226
|
}
|
|
12112
12227
|
if (structure.progressBar) {
|
|
12113
12228
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
@@ -12331,29 +12446,34 @@ set(value) {
|
|
|
12331
12446
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12332
12447
|
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12333
12448
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12334
|
-
underline: this.runtime.keyValueStyle?.underline
|
|
12449
|
+
underline: this.runtime.keyValueStyle?.underline,
|
|
12450
|
+
highlightText: this.runtime.keyHighlight
|
|
12335
12451
|
},
|
|
12336
12452
|
keyDescr: {
|
|
12337
12453
|
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12338
12454
|
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12339
12455
|
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12340
|
-
underline: this.runtime.keyValueDescrStyle?.underline
|
|
12456
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12457
|
+
highlightText: this.runtime.keyHighlight
|
|
12341
12458
|
},
|
|
12342
12459
|
baselineValue: {
|
|
12343
12460
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12344
12461
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12345
12462
|
underline: this.runtime.baselineStyle?.underline,
|
|
12346
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12463
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12464
|
+
highlightText: this.runtime.baselineHighlight
|
|
12347
12465
|
},
|
|
12348
12466
|
baselineDescr: {
|
|
12349
12467
|
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12350
12468
|
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12351
12469
|
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12352
|
-
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor
|
|
12470
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12471
|
+
highlightText: this.runtime.baselineHighlight
|
|
12353
12472
|
},
|
|
12354
12473
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar ? void 0 : {
|
|
12355
12474
|
size: this.keyValue ? .8 * baselineValueFontSize : 0,
|
|
12356
|
-
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor
|
|
12475
|
+
color: this.runtime.baselineColor || this.runtime.baselineStyle?.textColor || this.secondaryFontColor,
|
|
12476
|
+
highlight: this.runtime.baselineHighlight
|
|
12357
12477
|
}
|
|
12358
12478
|
};
|
|
12359
12479
|
}
|
|
@@ -12449,11 +12569,14 @@ set(value) {
|
|
|
12449
12569
|
});
|
|
12450
12570
|
(0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
|
|
12451
12571
|
}
|
|
12572
|
+
config(canvasRect, zoom) {
|
|
12573
|
+
return getScorecardConfiguration(getZoomedRect(1 / zoom, canvasRect), this.runtime);
|
|
12574
|
+
}
|
|
12452
12575
|
createChart() {
|
|
12453
12576
|
const canvas = this.canvas();
|
|
12454
12577
|
if (!canvas) return;
|
|
12455
12578
|
const zoom = this.env.model.getters.getViewportZoomLevel();
|
|
12456
|
-
drawScoreChart(
|
|
12579
|
+
drawScoreChart(this.config(canvas.getBoundingClientRect(), zoom), canvas, zoom);
|
|
12457
12580
|
}
|
|
12458
12581
|
};
|
|
12459
12582
|
|
|
@@ -28057,7 +28180,7 @@ set(value) {
|
|
|
28057
28180
|
*/
|
|
28058
28181
|
function filterInvalidHierarchicalPoints(values, hierarchy) {
|
|
28059
28182
|
const numberOfDataPoints = Math.max(values.length, ...hierarchy.map((dataset) => dataset.data?.length || 0));
|
|
28060
|
-
const isEmpty = (value) => value === null || value === "";
|
|
28183
|
+
const isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
28061
28184
|
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
|
|
28062
28185
|
const groups = hierarchy.map((dataset) => dataset.data?.[dataPointIndex]);
|
|
28063
28186
|
if (isEmpty(groups[0]?.value)) return false;
|
|
@@ -28621,23 +28744,11 @@ set(value) {
|
|
|
28621
28744
|
}
|
|
28622
28745
|
function getCalendarChartShowValues(definition, args) {
|
|
28623
28746
|
const { locale, axisFormats } = args;
|
|
28624
|
-
let background = (_value, dataset, index) => definition.background;
|
|
28625
|
-
const values = args.dataSetsValues.flat().flatMap((dsv) => dsv?.data.filter(isNumberResult)).map((cell) => cell.value);
|
|
28626
|
-
if (values.length) {
|
|
28627
|
-
const min = Math.min(...values);
|
|
28628
|
-
const max = Math.max(...values);
|
|
28629
|
-
const colorScale = getRuntimeColorScale(definition.colorScale ?? schemeToColorScale("oranges"), min, max);
|
|
28630
|
-
background = (_value, dataset, index) => {
|
|
28631
|
-
const value = dataset._dataset.values[index];
|
|
28632
|
-
if (value === void 0) return definition.background;
|
|
28633
|
-
return chartFontColor(colorScale(value));
|
|
28634
|
-
};
|
|
28635
|
-
}
|
|
28636
28747
|
return {
|
|
28637
28748
|
type: "calendar",
|
|
28638
28749
|
horizontal: false,
|
|
28639
28750
|
showValues: "showValues" in definition ? !!definition.showValues : false,
|
|
28640
|
-
background,
|
|
28751
|
+
background: () => definition.background,
|
|
28641
28752
|
callback: (_value, dataset, index) => {
|
|
28642
28753
|
const value = dataset._dataset.values[index];
|
|
28643
28754
|
return value === void 0 ? "" : humanizeNumber({
|
|
@@ -34538,6 +34649,35 @@ set(value) {
|
|
|
34538
34649
|
}
|
|
34539
34650
|
};
|
|
34540
34651
|
|
|
34652
|
+
//#endregion
|
|
34653
|
+
//#region src/components/side_panel/criterion_form/calendar_button/calendar_button.ts
|
|
34654
|
+
var CalendarButton = class extends Component {
|
|
34655
|
+
static template = "o-spreadsheet-CalendarButton";
|
|
34656
|
+
props = (0, _odoo_owl.props)({
|
|
34657
|
+
value: types.string().optional(""),
|
|
34658
|
+
onChange: types.function()
|
|
34659
|
+
});
|
|
34660
|
+
datePickerRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
34661
|
+
openCalendar() {
|
|
34662
|
+
this.datePickerRef()?.showPicker();
|
|
34663
|
+
}
|
|
34664
|
+
formatDateForInput(value) {
|
|
34665
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34666
|
+
return dateValue ? formatValue(dateValue.value, {
|
|
34667
|
+
format: "yyyy-mm-dd",
|
|
34668
|
+
locale: DEFAULT_LOCALE
|
|
34669
|
+
}) : "";
|
|
34670
|
+
}
|
|
34671
|
+
onDateInputValueChanged(value) {
|
|
34672
|
+
const dateValue = parseDateTime(value, DEFAULT_LOCALE);
|
|
34673
|
+
const formattedValue = dateValue ? formatValue(dateValue.value, {
|
|
34674
|
+
format: DEFAULT_LOCALE.dateFormat,
|
|
34675
|
+
locale: DEFAULT_LOCALE
|
|
34676
|
+
}) : "";
|
|
34677
|
+
this.props.onChange(formattedValue);
|
|
34678
|
+
}
|
|
34679
|
+
};
|
|
34680
|
+
|
|
34541
34681
|
//#endregion
|
|
34542
34682
|
//#region src/components/side_panel/criterion_form/criterion_form.ts
|
|
34543
34683
|
var CriterionForm = class extends Component {
|
|
@@ -35226,7 +35366,8 @@ set(value) {
|
|
|
35226
35366
|
static template = "o-spreadsheet-DataValidationDateCriterion";
|
|
35227
35367
|
static components = {
|
|
35228
35368
|
CriterionInput,
|
|
35229
|
-
Select
|
|
35369
|
+
Select,
|
|
35370
|
+
CalendarButton
|
|
35230
35371
|
};
|
|
35231
35372
|
get currentDateValue() {
|
|
35232
35373
|
return this.props.criterion.dateValue || "exactDate";
|
|
@@ -35252,7 +35393,10 @@ set(value) {
|
|
|
35252
35393
|
//#region src/components/side_panel/criterion_form/double_input_criterion/double_input_criterion.ts
|
|
35253
35394
|
var DoubleInputCriterionForm = class extends CriterionForm {
|
|
35254
35395
|
static template = "o-spreadsheet-DoubleInputCriterionForm";
|
|
35255
|
-
static components = {
|
|
35396
|
+
static components = {
|
|
35397
|
+
CriterionInput,
|
|
35398
|
+
CalendarButton
|
|
35399
|
+
};
|
|
35256
35400
|
onFirstValueChanged(value) {
|
|
35257
35401
|
const values = this.props.criterion.values;
|
|
35258
35402
|
this.updateCriterion({ values: [value, values[1] || ""] });
|
|
@@ -35261,6 +35405,9 @@ set(value) {
|
|
|
35261
35405
|
const values = this.props.criterion.values;
|
|
35262
35406
|
this.updateCriterion({ values: [values[0] || "", value] });
|
|
35263
35407
|
}
|
|
35408
|
+
get isDateType() {
|
|
35409
|
+
return ["dateIsBetween", "dateIsNotBetween"].includes(this.props.criterion.type);
|
|
35410
|
+
}
|
|
35264
35411
|
};
|
|
35265
35412
|
|
|
35266
35413
|
//#endregion
|
|
@@ -46607,7 +46754,7 @@ set(value) {
|
|
|
46607
46754
|
horizontalSnap: void 0,
|
|
46608
46755
|
verticalSnap: void 0,
|
|
46609
46756
|
cancelDnd: void 0,
|
|
46610
|
-
|
|
46757
|
+
overlappingChartOrCarousel: void 0
|
|
46611
46758
|
});
|
|
46612
46759
|
setup() {
|
|
46613
46760
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -46623,7 +46770,7 @@ set(value) {
|
|
|
46623
46770
|
this.dnd.selectedRect = void 0;
|
|
46624
46771
|
this.dnd.horizontalSnap = void 0;
|
|
46625
46772
|
this.dnd.verticalSnap = void 0;
|
|
46626
|
-
this.dnd.
|
|
46773
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46627
46774
|
this.dnd.cancelDnd = void 0;
|
|
46628
46775
|
}
|
|
46629
46776
|
});
|
|
@@ -46761,11 +46908,11 @@ set(value) {
|
|
|
46761
46908
|
hasStartedDnd = true;
|
|
46762
46909
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, getters.getActiveSheetScrollInfo());
|
|
46763
46910
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
46764
|
-
let
|
|
46911
|
+
let overlappingChartOrCarousel = void 0;
|
|
46765
46912
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
46766
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart"))
|
|
46767
|
-
this.dnd.
|
|
46768
|
-
if (!
|
|
46913
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
|
|
46914
|
+
this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
|
|
46915
|
+
if (!overlappingChartOrCarousel) {
|
|
46769
46916
|
const snapReturn = snapForMove(getters, selectedFigures, otherFigures);
|
|
46770
46917
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
46771
46918
|
this.dnd.selectedRect = this.getDndFigureRect();
|
|
@@ -46786,7 +46933,7 @@ set(value) {
|
|
|
46786
46933
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
46787
46934
|
return;
|
|
46788
46935
|
}
|
|
46789
|
-
if (!this.dnd.
|
|
46936
|
+
if (!this.dnd.overlappingChartOrCarousel) {
|
|
46790
46937
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
46791
46938
|
return {
|
|
46792
46939
|
sheetId,
|
|
@@ -46796,20 +46943,25 @@ set(value) {
|
|
|
46796
46943
|
}) || [];
|
|
46797
46944
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
46798
46945
|
} else {
|
|
46799
|
-
const
|
|
46946
|
+
const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
|
|
46800
46947
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
46801
|
-
this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46948
|
+
if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
46802
46949
|
sheetId,
|
|
46803
|
-
carouselFigureId,
|
|
46950
|
+
carouselFigureId: overlappingFigureId,
|
|
46804
46951
|
chartFigureIds
|
|
46805
46952
|
});
|
|
46953
|
+
else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
46954
|
+
sheetId,
|
|
46955
|
+
baseFigureId: overlappingFigureId,
|
|
46956
|
+
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
46957
|
+
});
|
|
46806
46958
|
}
|
|
46807
46959
|
this.dnd.draggedFigure = void 0;
|
|
46808
46960
|
this.dnd.selectedFigures = void 0;
|
|
46809
46961
|
this.dnd.selectedRect = void 0;
|
|
46810
46962
|
this.dnd.horizontalSnap = void 0;
|
|
46811
46963
|
this.dnd.verticalSnap = void 0;
|
|
46812
|
-
this.dnd.
|
|
46964
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46813
46965
|
};
|
|
46814
46966
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46815
46967
|
}
|
|
@@ -46865,7 +47017,7 @@ set(value) {
|
|
|
46865
47017
|
this.dnd.selectedRect = void 0;
|
|
46866
47018
|
this.dnd.horizontalSnap = void 0;
|
|
46867
47019
|
this.dnd.verticalSnap = void 0;
|
|
46868
|
-
this.dnd.
|
|
47020
|
+
this.dnd.overlappingChartOrCarousel = void 0;
|
|
46869
47021
|
};
|
|
46870
47022
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
46871
47023
|
}
|
|
@@ -46875,12 +47027,12 @@ set(value) {
|
|
|
46875
47027
|
getFigureStyle(figureUI) {
|
|
46876
47028
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
46877
47029
|
return cssPropertiesToCss({
|
|
46878
|
-
opacity: this.dnd.
|
|
47030
|
+
opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
|
|
46879
47031
|
cursor: "grabbing"
|
|
46880
47032
|
});
|
|
46881
47033
|
}
|
|
46882
47034
|
getFigureClass(figureUI) {
|
|
46883
|
-
if (figureUI.id !== this.dnd.
|
|
47035
|
+
if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
|
|
46884
47036
|
return "o-add-to-carousel";
|
|
46885
47037
|
}
|
|
46886
47038
|
getSnap(snapLine) {
|
|
@@ -46923,18 +47075,18 @@ set(value) {
|
|
|
46923
47075
|
height: `100%`
|
|
46924
47076
|
});
|
|
46925
47077
|
}
|
|
46926
|
-
|
|
47078
|
+
getOverlappingFigure(figureUI, otherFigures) {
|
|
46927
47079
|
if (figureUI.tag !== "chart") return;
|
|
46928
47080
|
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
46929
47081
|
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
46930
47082
|
let bestMatch;
|
|
46931
47083
|
let smallestDistance = Infinity;
|
|
46932
47084
|
for (const figure of otherFigures) {
|
|
46933
|
-
if (figure.tag !== "carousel") continue;
|
|
46934
|
-
const
|
|
46935
|
-
const
|
|
46936
|
-
const distanceX = Math.abs(figureCenterX -
|
|
46937
|
-
const distanceY = Math.abs(figureCenterY -
|
|
47085
|
+
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
47086
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
47087
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
47088
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
47089
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
46938
47090
|
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
46939
47091
|
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
46940
47092
|
smallestDistance = squaredDistance;
|
|
@@ -53027,11 +53179,16 @@ set(value) {
|
|
|
53027
53179
|
const deltaStyle = {};
|
|
53028
53180
|
let hasDelta = false;
|
|
53029
53181
|
const styleSheet = this.style[position.sheetId];
|
|
53030
|
-
if (!styleSheet) continue;
|
|
53031
53182
|
for (const key in newDefaultStyle) {
|
|
53032
53183
|
if (key in cellStyle) continue;
|
|
53033
|
-
const defaults = styleSheet[key];
|
|
53034
|
-
if (!defaults)
|
|
53184
|
+
const defaults = styleSheet?.[key];
|
|
53185
|
+
if (!defaults) {
|
|
53186
|
+
if (newDefaultStyle[key] !== DEFAULT_STYLE[key]) {
|
|
53187
|
+
deltaStyle[key] = DEFAULT_STYLE[key];
|
|
53188
|
+
hasDelta = true;
|
|
53189
|
+
}
|
|
53190
|
+
continue;
|
|
53191
|
+
}
|
|
53035
53192
|
const rowDefault = defaults.rowDefault?.[position.row];
|
|
53036
53193
|
if (rowDefault !== void 0) {
|
|
53037
53194
|
if (priorities.shouldUseDefaultRow) {
|
|
@@ -53073,7 +53230,8 @@ set(value) {
|
|
|
53073
53230
|
for (const key in styleSheet) if (!(key in style)) {
|
|
53074
53231
|
const defaults = styleSheet[key];
|
|
53075
53232
|
if (!defaults) continue;
|
|
53076
|
-
|
|
53233
|
+
const styleValue = defaults.rowDefault?.[position.row] ?? defaults.colDefault?.[position.col] ?? defaults.sheetDefault;
|
|
53234
|
+
if (styleValue !== void 0) style[key] = styleValue;
|
|
53077
53235
|
}
|
|
53078
53236
|
return style;
|
|
53079
53237
|
}
|
|
@@ -58721,13 +58879,13 @@ set(value) {
|
|
|
58721
58879
|
const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
|
|
58722
58880
|
const midValue = rule.midpoint ? this.parsePoint(sheetId, range, rule.midpoint) : null;
|
|
58723
58881
|
const maxValue = this.parsePoint(sheetId, range, rule.maximum, "max");
|
|
58724
|
-
if (minValue === null || maxValue === null || minValue >= maxValue || midValue && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58882
|
+
if (minValue === null || maxValue === null || minValue >= maxValue || midValue !== null && (minValue >= midValue || midValue >= maxValue)) return;
|
|
58725
58883
|
const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
|
|
58726
58884
|
const colorThresholds = [{
|
|
58727
58885
|
value: minValue,
|
|
58728
58886
|
color: rule.minimum.color
|
|
58729
58887
|
}];
|
|
58730
|
-
if (rule.midpoint && midValue) colorThresholds.push({
|
|
58888
|
+
if (rule.midpoint && midValue !== null) colorThresholds.push({
|
|
58731
58889
|
value: midValue,
|
|
58732
58890
|
color: rule.midpoint.color
|
|
58733
58891
|
});
|
|
@@ -66186,7 +66344,9 @@ set(value) {
|
|
|
66186
66344
|
ctx.lineWidth = 1.5 * thinLineWidth;
|
|
66187
66345
|
const isDarkMode = this.getters.isDarkMode();
|
|
66188
66346
|
for (const zone of zones) {
|
|
66347
|
+
if (!viewports.isZoneVisibleInViewport(sheetId, zone)) continue;
|
|
66189
66348
|
const { x, y, width, height } = viewports.getVisibleRect(sheetId, zone);
|
|
66349
|
+
const currentLineWidth = ctx.lineWidth;
|
|
66190
66350
|
if (!isDarkMode) ctx.globalCompositeOperation = "multiply";
|
|
66191
66351
|
if (height === 0 || width === 0) ctx.lineWidth = 3 * thinLineWidth;
|
|
66192
66352
|
if (height === 0 && width === 0) {
|
|
@@ -66200,6 +66360,7 @@ set(value) {
|
|
|
66200
66360
|
ctx.globalCompositeOperation = "source-over";
|
|
66201
66361
|
ctx.strokeRect(x, y, width, height);
|
|
66202
66362
|
}
|
|
66363
|
+
ctx.lineWidth = currentLineWidth;
|
|
66203
66364
|
}
|
|
66204
66365
|
ctx.globalCompositeOperation = "source-over";
|
|
66205
66366
|
const position = renderingContext.activePosition;
|
|
@@ -66346,6 +66507,9 @@ set(value) {
|
|
|
66346
66507
|
this.adjustViewportZoneX();
|
|
66347
66508
|
this.adjustViewportZoneY();
|
|
66348
66509
|
}
|
|
66510
|
+
isZoneVisible(zone) {
|
|
66511
|
+
return intersection(zone, this) !== void 0;
|
|
66512
|
+
}
|
|
66349
66513
|
/**
|
|
66350
66514
|
*
|
|
66351
66515
|
* Computes the visible coordinates & dimensions of a given zone inside the viewport
|
|
@@ -66626,6 +66790,9 @@ set(value) {
|
|
|
66626
66790
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
66627
66791
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
66628
66792
|
}
|
|
66793
|
+
isZoneVisibleInViewport(sheetId, zone) {
|
|
66794
|
+
return this.getSubViewports(sheetId).some((pane) => pane.isZoneVisible(zone));
|
|
66795
|
+
}
|
|
66629
66796
|
getScrollBarWidth() {
|
|
66630
66797
|
return 15 / this.zoomLevel;
|
|
66631
66798
|
}
|
|
@@ -70699,18 +70866,28 @@ set(value) {
|
|
|
70699
70866
|
return;
|
|
70700
70867
|
}
|
|
70701
70868
|
const activeSheetId = this.env.model.getters.getActiveSheetId();
|
|
70702
|
-
if (activeSheetId !== sheetId)
|
|
70703
|
-
|
|
70704
|
-
|
|
70705
|
-
|
|
70706
|
-
|
|
70869
|
+
if (activeSheetId !== sheetId) {
|
|
70870
|
+
if (!this.env.model.getters.getSheet(sheetId).isVisible) {
|
|
70871
|
+
this.env.notifyUser({
|
|
70872
|
+
text: _t("The sheet on which the range is defined is hidden."),
|
|
70873
|
+
type: "info",
|
|
70874
|
+
sticky: false
|
|
70875
|
+
});
|
|
70876
|
+
return;
|
|
70877
|
+
}
|
|
70878
|
+
this.env.model.dispatch("ACTIVATE_SHEET", {
|
|
70879
|
+
sheetIdFrom: activeSheetId,
|
|
70880
|
+
sheetIdTo: sheetId
|
|
70881
|
+
});
|
|
70882
|
+
}
|
|
70883
|
+
this.env.model.selection.selectCell(zone.right, zone.bottom, { allowsHiddenSelection: true });
|
|
70707
70884
|
this.env.model.selection.selectZone({
|
|
70708
70885
|
cell: {
|
|
70709
70886
|
col: zone.left,
|
|
70710
70887
|
row: zone.top
|
|
70711
70888
|
},
|
|
70712
70889
|
zone
|
|
70713
|
-
});
|
|
70890
|
+
}, { allowsHiddenSelection: true });
|
|
70714
70891
|
}
|
|
70715
70892
|
get selectionKey() {
|
|
70716
70893
|
return `${this.env.model.getters.getActiveSheetId()}-${zoneToXc(this.selectedZone)}`;
|
|
@@ -76989,8 +77166,8 @@ set(value) {
|
|
|
76989
77166
|
for (const key in DEFAULT_STYLE) {
|
|
76990
77167
|
content.style[key] = {
|
|
76991
77168
|
sheetDefault: this.getters.getDefaultStyle(data.sheetId, key, "SHEET", void 0) ?? DEFAULT_STYLE[key],
|
|
76992
|
-
colDefault:
|
|
76993
|
-
rowDefault:
|
|
77169
|
+
colDefault: [],
|
|
77170
|
+
rowDefault: []
|
|
76994
77171
|
};
|
|
76995
77172
|
let colIndex = 0;
|
|
76996
77173
|
for (const col of data.columnsIndexes) {
|
|
@@ -77007,20 +77184,41 @@ set(value) {
|
|
|
77007
77184
|
}
|
|
77008
77185
|
return content;
|
|
77009
77186
|
}
|
|
77010
|
-
|
|
77011
|
-
|
|
77012
|
-
|
|
77013
|
-
|
|
77014
|
-
|
|
77015
|
-
|
|
77187
|
+
adaptContentToZone(zone, content) {
|
|
77188
|
+
const colRepetition = Math.max(Math.floor((zone.right - zone.left + 1) / content.width), 1);
|
|
77189
|
+
const rowRepetition = Math.max(Math.floor((zone.bottom - zone.top + 1) / content.height), 1);
|
|
77190
|
+
if (colRepetition === 1 && rowRepetition === 1) return content;
|
|
77191
|
+
const newContent = deepCopy(content);
|
|
77192
|
+
newContent.height *= rowRepetition;
|
|
77193
|
+
newContent.width *= colRepetition;
|
|
77194
|
+
if (rowRepetition > 1 && newContent.format.rowDefault) {
|
|
77195
|
+
newContent.format.rowDefault.length = content.height;
|
|
77196
|
+
newContent.format.rowDefault = repeat(newContent.format.rowDefault, rowRepetition);
|
|
77197
|
+
}
|
|
77198
|
+
if (colRepetition > 1 && newContent.format.colDefault) {
|
|
77199
|
+
newContent.format.colDefault.length = content.width;
|
|
77200
|
+
newContent.format.colDefault = repeat(newContent.format.colDefault, colRepetition);
|
|
77201
|
+
}
|
|
77202
|
+
for (const key in content.style) {
|
|
77203
|
+
if (rowRepetition > 1 && newContent.style[key].rowDefault) {
|
|
77204
|
+
newContent.style[key].rowDefault.length = content.height;
|
|
77205
|
+
newContent.style[key].rowDefault = repeat(newContent.style[key].rowDefault, rowRepetition);
|
|
77206
|
+
}
|
|
77207
|
+
if (colRepetition > 1 && newContent.style[key].colDefault) {
|
|
77208
|
+
newContent.style[key].colDefault.length = content.width;
|
|
77209
|
+
newContent.style[key].colDefault = repeat(newContent.style[key].colDefault, colRepetition);
|
|
77210
|
+
}
|
|
77211
|
+
}
|
|
77212
|
+
return newContent;
|
|
77016
77213
|
}
|
|
77017
77214
|
paste(target, content, options) {
|
|
77018
77215
|
const sheetId = target.sheetId;
|
|
77019
77216
|
if (options.pasteOption === "asValue") return;
|
|
77020
77217
|
const zones = target.zones;
|
|
77021
|
-
if (!options.isCutOperation) for (const zone of zones)
|
|
77022
|
-
|
|
77023
|
-
this.
|
|
77218
|
+
if (!options.isCutOperation) for (const zone of zones) {
|
|
77219
|
+
const newContent = this.adaptContentToZone(zone, content);
|
|
77220
|
+
this.pasteStyle(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.style);
|
|
77221
|
+
this.pasteFormat(sheetId, zone.left, zone.top, newContent.width, newContent.height, newContent.format);
|
|
77024
77222
|
}
|
|
77025
77223
|
else {
|
|
77026
77224
|
this.clearClippedZones(content);
|
|
@@ -77133,7 +77331,7 @@ set(value) {
|
|
|
77133
77331
|
const commands = [];
|
|
77134
77332
|
for (const [zoneStr, [zone, priority]] of Object.entries(zones)) {
|
|
77135
77333
|
const format = updateCells[zoneStr];
|
|
77136
|
-
commands.push([
|
|
77334
|
+
if (format !== void 0) commands.push([
|
|
77137
77335
|
priority,
|
|
77138
77336
|
zone,
|
|
77139
77337
|
format
|
|
@@ -87335,7 +87533,9 @@ set(value) {
|
|
|
87335
87533
|
collapseHierarchicalDisplayName,
|
|
87336
87534
|
getCanonicalSymbolName,
|
|
87337
87535
|
fuzzyLookup,
|
|
87338
|
-
replaceSymbolInFormula
|
|
87536
|
+
replaceSymbolInFormula,
|
|
87537
|
+
isSingleCellReference,
|
|
87538
|
+
computeCachedTextDimension
|
|
87339
87539
|
};
|
|
87340
87540
|
const links = {
|
|
87341
87541
|
isMarkdownLink,
|
|
@@ -87405,7 +87605,8 @@ set(value) {
|
|
|
87405
87605
|
FullScreenFigure,
|
|
87406
87606
|
NumberInput,
|
|
87407
87607
|
TopBar,
|
|
87408
|
-
Composer
|
|
87608
|
+
Composer,
|
|
87609
|
+
CalendarButton
|
|
87409
87610
|
};
|
|
87410
87611
|
const hooks = {
|
|
87411
87612
|
useDragAndDropListItems,
|
|
@@ -87559,8 +87760,8 @@ exports.stores = stores;
|
|
|
87559
87760
|
exports.tokenColors = tokenColors;
|
|
87560
87761
|
exports.tokenize = tokenize;
|
|
87561
87762
|
|
|
87562
|
-
__info__.version = "19.5.0-alpha.
|
|
87563
|
-
__info__.date = "2026-07-
|
|
87564
|
-
__info__.hash = "
|
|
87763
|
+
__info__.version = "19.5.0-alpha.3";
|
|
87764
|
+
__info__.date = "2026-07-14T10:17:06.949Z";
|
|
87765
|
+
__info__.hash = "c029184";
|
|
87565
87766
|
|
|
87566
87767
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|