@odoo/o-spreadsheet 18.1.0-alpha.3 → 18.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.1.0-alpha.3
6
- * @date 2024-11-08T12:18:22.841Z
7
- * @hash 96dcfab
5
+ * @version 18.1.0-alpha.4
6
+ * @date 2024-11-13T15:06:47.769Z
7
+ * @hash e1ad985
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -2561,6 +2561,18 @@ function groupItemIdsByZones(positionsByItemId) {
2561
2561
  }
2562
2562
  return result;
2563
2563
  }
2564
+ function* iterateItemIdsPositions(sheetId, itemIdsByZones) {
2565
+ for (const zoneXc in itemIdsByZones) {
2566
+ const zone = toZone(zoneXc);
2567
+ const itemId = itemIdsByZones[zoneXc];
2568
+ for (let row = zone.top; row <= zone.bottom; row++) {
2569
+ for (let col = zone.left; col <= zone.right; col++) {
2570
+ const position = { sheetId, col, row };
2571
+ yield [position, itemId];
2572
+ }
2573
+ }
2574
+ }
2575
+ }
2564
2576
 
2565
2577
  // -----------------------------------------------------------------------------
2566
2578
  // Date Type
@@ -9254,22 +9266,6 @@ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9254
9266
  y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9255
9267
  }; // Position at the center of the scrollable viewport
9256
9268
  }
9257
- function getChartAxisTitleRuntime(design) {
9258
- if (design?.title?.text) {
9259
- const { text, color, align, italic, bold } = design.title;
9260
- return {
9261
- display: true,
9262
- text,
9263
- color,
9264
- font: {
9265
- style: italic ? "italic" : "normal",
9266
- weight: bold ? "bold" : "normal",
9267
- },
9268
- align: align === "left" ? "start" : align === "right" ? "end" : "center",
9269
- };
9270
- }
9271
- return;
9272
- }
9273
9269
  function getDefinedAxis(definition) {
9274
9270
  let useLeftAxis = false, useRightAxis = false;
9275
9271
  if ("horizontal" in definition && definition.horizontal) {
@@ -9286,148 +9282,6 @@ function getDefinedAxis(definition) {
9286
9282
  useLeftAxis ||= !useRightAxis;
9287
9283
  return { useLeftAxis, useRightAxis };
9288
9284
  }
9289
- function getChartAxis(definition, position, type, options) {
9290
- const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
9291
- if ((position === "left" && !useLeftAxis) || (position === "right" && !useRightAxis)) {
9292
- return undefined;
9293
- }
9294
- const fontColor = chartFontColor(definition.background);
9295
- let design;
9296
- if (position === "bottom") {
9297
- design = definition.axesDesign?.x;
9298
- }
9299
- else if (position === "left") {
9300
- design = definition.axesDesign?.y;
9301
- }
9302
- else {
9303
- design = definition.axesDesign?.y1;
9304
- }
9305
- if (type === "values") {
9306
- const displayGridLines = position === "left" ||
9307
- (position === "right" && !useLeftAxis) ||
9308
- (definition.type === "bar" && definition.horizontal === true);
9309
- return {
9310
- position: position,
9311
- title: getChartAxisTitleRuntime(design),
9312
- grid: {
9313
- display: displayGridLines,
9314
- },
9315
- beginAtZero: true,
9316
- stacked: options?.stacked,
9317
- ticks: {
9318
- color: fontColor,
9319
- callback: formatTickValue(options),
9320
- },
9321
- };
9322
- }
9323
- else {
9324
- return {
9325
- ticks: {
9326
- padding: 5,
9327
- color: fontColor,
9328
- },
9329
- grid: {
9330
- display: definition.type === "scatter",
9331
- },
9332
- stacked: options?.stacked,
9333
- title: getChartAxisTitleRuntime(design),
9334
- };
9335
- }
9336
- }
9337
- function computeChartPadding({ displayTitle, displayLegend, }) {
9338
- let top = 25;
9339
- if (displayTitle) {
9340
- top = 0;
9341
- }
9342
- else if (displayLegend) {
9343
- top = 10;
9344
- }
9345
- return { left: 20, right: 20, top, bottom: 10 };
9346
- }
9347
- function getTrendDatasetForBarChart(config, dataset) {
9348
- const filteredValues = [];
9349
- const filteredLabels = [];
9350
- const labels = [];
9351
- for (let i = 0; i < dataset.data.length; i++) {
9352
- if (typeof dataset.data[i] === "number") {
9353
- filteredValues.push(dataset.data[i]);
9354
- filteredLabels.push(i + 1);
9355
- }
9356
- labels.push(i + 1);
9357
- }
9358
- const newLabels = range(0.5, labels.length + 0.55, 0.2);
9359
- const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
9360
- if (!newValues.length) {
9361
- return;
9362
- }
9363
- return getFullTrendingLineDataSet(dataset, config, newValues);
9364
- }
9365
- function getFullTrendingLineDataSet(dataset, config, data) {
9366
- const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
9367
- defaultBorderColor.a = 1;
9368
- const borderColor = config.color || lightenColor(rgbaToHex(defaultBorderColor), 0.5);
9369
- const backgroundRGBA = colorToRGBA(borderColor);
9370
- // @ts-expect-error
9371
- if (dataset?.fill) {
9372
- backgroundRGBA.a = LINE_FILL_TRANSPARENCY; // to support area charts
9373
- }
9374
- return {
9375
- ...dataset,
9376
- type: "line",
9377
- xAxisID: config.type !== "trailingMovingAverage" ? TREND_LINE_XAXIS_ID : "x",
9378
- label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
9379
- data,
9380
- order: -1,
9381
- showLine: true,
9382
- pointRadius: 0,
9383
- backgroundColor: rgbaToHex(backgroundRGBA),
9384
- borderColor,
9385
- borderDash: [5, 5],
9386
- borderWidth: undefined,
9387
- };
9388
- }
9389
- function interpolateData(config, values, labels, newLabels) {
9390
- if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
9391
- return [];
9392
- }
9393
- const labelMin = Math.min(...labels);
9394
- const labelMax = Math.max(...labels);
9395
- const labelRange = labelMax - labelMin;
9396
- const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
9397
- const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
9398
- switch (config.type) {
9399
- case "polynomial": {
9400
- const order = config.order ?? 2;
9401
- if (order === 1) {
9402
- return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
9403
- }
9404
- const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
9405
- return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
9406
- }
9407
- case "exponential": {
9408
- const positiveLogValues = [];
9409
- const filteredLabels = [];
9410
- for (let i = 0; i < values.length; i++) {
9411
- if (values[i] > 0) {
9412
- positiveLogValues.push(Math.log(values[i]));
9413
- filteredLabels.push(normalizedLabels[i]);
9414
- }
9415
- }
9416
- if (!filteredLabels.length) {
9417
- return [];
9418
- }
9419
- return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
9420
- }
9421
- case "logarithmic": {
9422
- return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
9423
- }
9424
- case "trailingMovingAverage": {
9425
- return getMovingAverageValues(values, config.window);
9426
- }
9427
- default:
9428
- return [];
9429
- }
9430
- }
9431
9285
  function formatChartDatasetValue(axisFormats, locale) {
9432
9286
  return (value, axisId) => {
9433
9287
  const format = axisId ? axisFormats?.[axisId] : undefined;
@@ -9446,67 +9300,17 @@ function formatTickValue(localeFormat) {
9446
9300
  });
9447
9301
  };
9448
9302
  }
9449
- function getChartColorsGenerator(definition, dataSetsSize) {
9450
- return new ColorGenerator(dataSetsSize, definition.dataSets.map((ds) => ds.backgroundColor));
9451
- }
9452
9303
  const CHART_AXIS_CHOICES = [
9453
9304
  { value: "left", label: _t("Left") },
9454
9305
  { value: "right", label: _t("Right") },
9455
9306
  ];
9456
- /* Callback used to make the legend interactive
9457
- * These are used to make the user able to hide/show a data series by
9458
- * clicking on the corresponding label in the legend. The onHover and
9459
- * onLeave callbacks are used to show a pointer when hovering an item
9460
- * of the legend so that the user knows it is clickable.
9461
- */
9462
- const INTERACTIVE_LEGEND_CONFIG = {
9463
- onHover: (event) => {
9464
- const target = event.native?.target;
9465
- if (!target) {
9466
- return;
9467
- }
9468
- //@ts-ignore
9469
- target.style.cursor = "pointer";
9470
- },
9471
- onLeave: (event) => {
9472
- const target = event.native?.target;
9473
- if (!target) {
9474
- return;
9475
- }
9476
- //@ts-ignore
9477
- target.style.cursor = "default";
9478
- },
9479
- onClick: (event, legendItem, legend) => {
9480
- if (!legend.legendItems) {
9481
- return;
9482
- }
9483
- const index = legend.legendItems.indexOf(legendItem);
9484
- if (legend.chart.isDatasetVisible(index)) {
9485
- legend.chart.hide(index);
9486
- }
9487
- else {
9488
- legend.chart.show(index);
9489
- }
9490
- event.native.preventDefault();
9491
- event.native.stopPropagation();
9492
- },
9493
- };
9494
- function getCustomLegendLabels(fontColor, legendLabelConfig) {
9495
- return {
9496
- labels: {
9497
- color: fontColor,
9498
- usePointStyle: true,
9499
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => ({
9500
- text: dataset.label ?? "",
9501
- fontColor,
9502
- strokeStyle: dataset.borderColor,
9503
- fillStyle: dataset.backgroundColor,
9504
- hidden: !chart.isDatasetVisible(index),
9505
- pointStyle: dataset.type === "line" ? "line" : "rect",
9506
- ...legendLabelConfig,
9507
- })),
9508
- },
9509
- };
9307
+ function getPieColors(colors, dataSetsValues) {
9308
+ const pieColors = [];
9309
+ const maxLength = largeMax(dataSetsValues.map((ds) => ds.data.length));
9310
+ for (let i = 0; i <= maxLength; i++) {
9311
+ pieColors.push(colors.next());
9312
+ }
9313
+ return pieColors;
9510
9314
  }
9511
9315
 
9512
9316
  /** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
@@ -9532,9 +9336,10 @@ const chartShowValuesPlugin = {
9532
9336
  break;
9533
9337
  case "bar":
9534
9338
  case "line":
9339
+ case "radar":
9535
9340
  options.horizontal
9536
9341
  ? drawHorizontalBarChartValues(chart, options, ctx)
9537
- : drawLineOrBarChartValues(chart, options, ctx);
9342
+ : drawLineOrBarOrRadarChartValues(chart, options, ctx);
9538
9343
  break;
9539
9344
  }
9540
9345
  ctx.restore();
@@ -9546,7 +9351,7 @@ function drawTextWithBackground(text, x, y, ctx) {
9546
9351
  ctx.lineWidth = 1;
9547
9352
  ctx.fillText(text, x, y);
9548
9353
  }
9549
- function drawLineOrBarChartValues(chart, options, ctx) {
9354
+ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
9550
9355
  const yMax = chart.chartArea.bottom;
9551
9356
  const yMin = chart.chartArea.top;
9552
9357
  const textsPositions = {};
@@ -9555,12 +9360,17 @@ function drawLineOrBarChartValues(chart, options, ctx) {
9555
9360
  return; // ignore trend lines
9556
9361
  }
9557
9362
  for (let i = 0; i < dataset._parsed.length; i++) {
9558
- const value = dataset._parsed[i].y;
9559
- const displayValue = options.callback(value - 0, dataset.yAxisID);
9363
+ const parsedValue = dataset._parsed[i];
9364
+ const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
9365
+ if (isNaN(value)) {
9366
+ continue;
9367
+ }
9368
+ const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
9369
+ const displayValue = options.callback(Number(value), axisId);
9560
9370
  const point = dataset.data[i];
9561
9371
  const xPosition = point.x;
9562
9372
  let yPosition = 0;
9563
- if (chart.config.type === "line") {
9373
+ if (chart.config.type === "line" || chart.config.type === "radar") {
9564
9374
  yPosition = point.y - 10;
9565
9375
  }
9566
9376
  else {
@@ -9593,8 +9403,11 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
9593
9403
  return; // ignore trend lines
9594
9404
  }
9595
9405
  for (let i = 0; i < dataset._parsed.length; i++) {
9596
- const value = dataset._parsed[i].x;
9597
- const displayValue = options.callback(value - 0, dataset.xAxisID);
9406
+ const value = Number(dataset._parsed[i].x);
9407
+ if (isNaN(value)) {
9408
+ continue;
9409
+ }
9410
+ const displayValue = options.callback(value, dataset.xAxisID);
9598
9411
  const point = dataset.data[i];
9599
9412
  const yPosition = point.y;
9600
9413
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -22497,6 +22310,81 @@ function toXlsxHexColor(color) {
22497
22310
  return color;
22498
22311
  }
22499
22312
 
22313
+ const CHART_COMMON_OPTIONS = {
22314
+ // https://www.chartjs.org/docs/latest/general/responsive.html
22315
+ responsive: true, // will resize when its container is resized
22316
+ maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
22317
+ elements: {
22318
+ line: {
22319
+ fill: false, // do not fill the area under line charts
22320
+ },
22321
+ point: {
22322
+ hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
22323
+ },
22324
+ },
22325
+ animation: false,
22326
+ };
22327
+ function truncateLabel(label) {
22328
+ if (!label) {
22329
+ return "";
22330
+ }
22331
+ if (label.length > MAX_CHAR_LABEL) {
22332
+ return label.substring(0, MAX_CHAR_LABEL) + "…";
22333
+ }
22334
+ return label;
22335
+ }
22336
+ function chartToImage(runtime, figure, type) {
22337
+ // wrap the canvas in a div with a fixed size because chart.js would
22338
+ // fill the whole page otherwise
22339
+ const div = document.createElement("div");
22340
+ div.style.width = `${figure.width}px`;
22341
+ div.style.height = `${figure.height}px`;
22342
+ const canvas = document.createElement("canvas");
22343
+ div.append(canvas);
22344
+ canvas.setAttribute("width", figure.width.toString());
22345
+ canvas.setAttribute("height", figure.height.toString());
22346
+ // we have to add the canvas to the DOM otherwise it won't be rendered
22347
+ document.body.append(div);
22348
+ if ("chartJsConfig" in runtime) {
22349
+ const config = deepCopy(runtime.chartJsConfig);
22350
+ config.plugins = [backgroundColorChartJSPlugin];
22351
+ const chart = new window.Chart(canvas, config);
22352
+ const imgContent = chart.toBase64Image();
22353
+ chart.destroy();
22354
+ div.remove();
22355
+ return imgContent;
22356
+ }
22357
+ else if (type === "scorecard") {
22358
+ const design = getScorecardConfiguration(figure, runtime);
22359
+ drawScoreChart(design, canvas);
22360
+ const imgContent = canvas.toDataURL();
22361
+ div.remove();
22362
+ return imgContent;
22363
+ }
22364
+ else if (type === "gauge") {
22365
+ drawGaugeChart(canvas, runtime);
22366
+ const imgContent = canvas.toDataURL();
22367
+ div.remove();
22368
+ return imgContent;
22369
+ }
22370
+ return undefined;
22371
+ }
22372
+ /**
22373
+ * Custom chart.js plugin to set the background color of the canvas
22374
+ * https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
22375
+ */
22376
+ const backgroundColorChartJSPlugin = {
22377
+ id: "customCanvasBackgroundColor",
22378
+ beforeDraw: (chart) => {
22379
+ const { ctx } = chart;
22380
+ ctx.save();
22381
+ ctx.globalCompositeOperation = "destination-over";
22382
+ ctx.fillStyle = "#ffffff";
22383
+ ctx.fillRect(0, 0, chart.width, chart.height);
22384
+ ctx.restore();
22385
+ },
22386
+ };
22387
+
22500
22388
  /**
22501
22389
  * Represent a raw XML string
22502
22390
  */
@@ -23998,12 +23886,9 @@ function convertWidthFromExcel(width) {
23998
23886
  return width;
23999
23887
  return Math.round((width / WIDTH_FACTOR) * 100) / 100;
24000
23888
  }
24001
- function extractStyle(cell, data) {
24002
- let style = {};
24003
- if (cell.style) {
24004
- style = data.styles[cell.style];
24005
- }
24006
- const format = extractFormat(cell, data);
23889
+ function extractStyle(data, styleId, formatId, borderId) {
23890
+ const style = styleId ? data.styles[styleId] : {};
23891
+ const format = formatId ? data.formats[formatId] : undefined;
24007
23892
  const styles = {
24008
23893
  font: {
24009
23894
  size: style?.fontSize || DEFAULT_FONT_SIZE,
@@ -24017,7 +23902,7 @@ function extractStyle(cell, data) {
24017
23902
  }
24018
23903
  : { reservedAttribute: "none" },
24019
23904
  numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
24020
- border: cell.border || 0,
23905
+ border: borderId || 0,
24021
23906
  alignment: {
24022
23907
  horizontal: style.align,
24023
23908
  vertical: style.verticalAlign
@@ -24032,12 +23917,6 @@ function extractStyle(cell, data) {
24032
23917
  styles.font["italic"] = !!style?.italic || undefined;
24033
23918
  return styles;
24034
23919
  }
24035
- function extractFormat(cell, data) {
24036
- if (cell.format) {
24037
- return data.formats[cell.format];
24038
- }
24039
- return undefined;
24040
- }
24041
23920
  function normalizeStyle(construct, styles) {
24042
23921
  // Normalize this
24043
23922
  const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
@@ -24501,9 +24380,7 @@ function convertCells(sheet, data, sheetDims, warningManager) {
24501
24380
  }, {});
24502
24381
  for (let row of sheet.rows) {
24503
24382
  for (let cell of row.cells) {
24504
- cells[cell.xc] = {
24505
- content: getCellValue(cell, hyperlinkMap, sharedStrings, warningManager),
24506
- };
24383
+ cells[cell.xc] = getCellValue(cell, hyperlinkMap, sharedStrings, warningManager);
24507
24384
  if (cell.styleIndex) {
24508
24385
  // + 1 : our indexes for normalized values begin at 1 and not 0
24509
24386
  styles[cell.xc] = cell.styleIndex + 1;
@@ -24516,11 +24393,6 @@ function convertCells(sheet, data, sheetDims, warningManager) {
24516
24393
  for (let row of sheet.rows.filter((row) => row.styleIndex)) {
24517
24394
  for (let colIndex = 1; colIndex <= sheetDims[0]; colIndex++) {
24518
24395
  const xc = toXC(colIndex - 1, row.index - 1); // Excel indexes start at 1
24519
- let cell = cells[xc];
24520
- if (!cell) {
24521
- cell = {};
24522
- cells[xc] = cell;
24523
- }
24524
24396
  styles[xc] ??= row.styleIndex + 1;
24525
24397
  borders[xc] ??= data.styles[row.styleIndex].borderId + 1;
24526
24398
  formats[xc] ??= data.styles[row.styleIndex].numFmtId + 1;
@@ -24531,11 +24403,6 @@ function convertCells(sheet, data, sheetDims, warningManager) {
24531
24403
  for (let colIndex = col.min; colIndex <= Math.min(col.max, sheetDims[0]); colIndex++) {
24532
24404
  for (let rowIndex = 1; rowIndex <= sheetDims[1]; rowIndex++) {
24533
24405
  const xc = toXC(colIndex - 1, rowIndex - 1); // Excel indexes start at 1
24534
- let cell = cells[xc];
24535
- if (!cell) {
24536
- cell = {};
24537
- cells[xc] = cell;
24538
- }
24539
24406
  styles[xc] ??= col.styleIndex + 1;
24540
24407
  borders[xc] ??= data.styles[col.styleIndex].borderId + 1;
24541
24408
  formats[xc] ??= data.styles[col.styleIndex].numFmtId + 1;
@@ -25028,11 +24895,11 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25028
24895
  const tabRef = table.name + "[";
25029
24896
  for (let position of positions(toZone(table.ref))) {
25030
24897
  const xc = toXC(position.col, position.row);
25031
- const cell = sheet.cells[xc];
25032
- if (cell && cell.content && cell.content.startsWith("=")) {
24898
+ let cellContent = sheet.cells[xc];
24899
+ if (cellContent?.startsWith("=")) {
25033
24900
  let refIndex;
25034
- while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {
25035
- let reference = cell.content.slice(refIndex + tabRef.length);
24901
+ while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
24902
+ let reference = cellContent.slice(refIndex + tabRef.length);
25036
24903
  // Expression can either be tableName[colName] or tableName[[#This Row], [colName]]
25037
24904
  let endIndex = reference.indexOf("]");
25038
24905
  if (reference.startsWith(`[`)) {
@@ -25041,11 +24908,12 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25041
24908
  }
25042
24909
  reference = reference.slice(0, endIndex);
25043
24910
  const convertedRef = convertTableReference(reference, table, xc);
25044
- cell.content =
25045
- cell.content.slice(0, refIndex) +
24911
+ cellContent =
24912
+ cellContent.slice(0, refIndex) +
25046
24913
  convertedRef +
25047
- cell.content.slice(tabRef.length + refIndex + endIndex + 1);
24914
+ cellContent.slice(tabRef.length + refIndex + endIndex + 1);
25048
24915
  }
24916
+ sheet.cells[xc] = cellContent;
25049
24917
  }
25050
24918
  }
25051
24919
  }
@@ -26552,7 +26420,7 @@ function getRelationFile(file, xmls) {
26552
26420
  return relsFile;
26553
26421
  }
26554
26422
 
26555
- const EXCEL_IMPORT_VERSION = 21;
26423
+ const EXCEL_IMPORT_VERSION = 24;
26556
26424
  class XlsxReader {
26557
26425
  warningManager;
26558
26426
  xmls;
@@ -27182,6 +27050,21 @@ migrationStepRegistry
27182
27050
  }
27183
27051
  return data;
27184
27052
  },
27053
+ })
27054
+ .add("migration_23", {
27055
+ // Flatten cell content: { content: "value" } -> "value"
27056
+ versionFrom: "23",
27057
+ migrate(data) {
27058
+ for (const sheet of data.sheets || []) {
27059
+ for (const xc in sheet.cells) {
27060
+ const cell = sheet.cells[xc];
27061
+ if (cell) {
27062
+ sheet.cells[xc] = cell.content;
27063
+ }
27064
+ }
27065
+ }
27066
+ return data;
27067
+ },
27185
27068
  });
27186
27069
  function fixOverlappingFilters(data) {
27187
27070
  for (let sheet of data.sheets || []) {
@@ -27209,7 +27092,7 @@ function fixOverlappingFilters(data) {
27209
27092
  * a breaking change is made in the way the state is handled, and an upgrade
27210
27093
  * function should be defined
27211
27094
  */
27212
- const CURRENT_VERSION = 23;
27095
+ const CURRENT_VERSION = 24;
27213
27096
  const INITIAL_SHEET_ID = "Sheet1";
27214
27097
  /**
27215
27098
  * This function tries to load anything that could look like a valid
@@ -27473,6 +27356,7 @@ function createEmptyExcelSheet(sheetId, name) {
27473
27356
  ...createEmptySheet(sheetId, name),
27474
27357
  charts: [],
27475
27358
  images: [],
27359
+ cellValues: {},
27476
27360
  };
27477
27361
  }
27478
27362
  function createEmptyExcelWorkbookData() {
@@ -27735,10 +27619,480 @@ const measureDisplayTerms = {
27735
27619
  },
27736
27620
  };
27737
27621
 
27622
+ const UNIT_LENGTH = {
27623
+ second: 1000,
27624
+ minute: 1000 * 60,
27625
+ hour: 1000 * 3600,
27626
+ day: 1000 * 3600 * 24,
27627
+ month: 1000 * 3600 * 24 * 30,
27628
+ year: 1000 * 3600 * 24 * 365,
27629
+ };
27630
+ const Milliseconds = {
27631
+ inSeconds: function (milliseconds) {
27632
+ return Math.floor(milliseconds / UNIT_LENGTH.second);
27633
+ },
27634
+ inMinutes: function (milliseconds) {
27635
+ return Math.floor(milliseconds / UNIT_LENGTH.minute);
27636
+ },
27637
+ inHours: function (milliseconds) {
27638
+ return Math.floor(milliseconds / UNIT_LENGTH.hour);
27639
+ },
27640
+ inDays: function (milliseconds) {
27641
+ return Math.floor(milliseconds / UNIT_LENGTH.day);
27642
+ },
27643
+ inMonths: function (milliseconds) {
27644
+ return Math.floor(milliseconds / UNIT_LENGTH.month);
27645
+ },
27646
+ inYears: function (milliseconds) {
27647
+ return Math.floor(milliseconds / UNIT_LENGTH.year);
27648
+ },
27649
+ };
27738
27650
  /**
27739
- * This file contains helpers that are common to different runtime charts (mainly
27740
- * line, bar and pie charts)
27651
+ * Regex to test if a format string is a date format that can be translated into a luxon time format
27652
+ */
27653
+ const timeFormatLuxonCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
27654
+ /** Get the time options for the XAxis of ChartJS */
27655
+ function getChartTimeOptions(labels, labelFormat, locale) {
27656
+ const luxonFormat = convertDateFormatForLuxon(labelFormat);
27657
+ const timeUnit = getBestTimeUnitForScale(labels, luxonFormat, locale);
27658
+ const displayFormats = {};
27659
+ if (timeUnit) {
27660
+ displayFormats[timeUnit] = luxonFormat;
27661
+ }
27662
+ return {
27663
+ parser: luxonFormat,
27664
+ displayFormats,
27665
+ unit: timeUnit ?? false,
27666
+ };
27667
+ }
27668
+ /**
27669
+ * Convert the given date format into a format that moment.js understands.
27670
+ *
27671
+ * https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens
27672
+ */
27673
+ function convertDateFormatForLuxon(format) {
27674
+ // "m" before "h" === month, "m" after "h" === minute
27675
+ const indexH = format.indexOf("h");
27676
+ if (indexH >= 0) {
27677
+ format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
27678
+ }
27679
+ else {
27680
+ format = format.replace(/m/g, "M");
27681
+ }
27682
+ // If we have an "a", we should display hours as AM/PM (h), otherwise display 24 hours format (H)
27683
+ if (!format.includes("a")) {
27684
+ format = format.replace(/h/g, "H");
27685
+ }
27686
+ return format;
27687
+ }
27688
+ /** Get the minimum time unit that the format is able to display */
27689
+ function getFormatMinDisplayUnit(format) {
27690
+ if (format.includes("s")) {
27691
+ return "second";
27692
+ }
27693
+ else if (format.includes("m")) {
27694
+ return "minute";
27695
+ }
27696
+ else if (format.includes("h") || format.includes("H")) {
27697
+ return "hour";
27698
+ }
27699
+ else if (format.includes("d")) {
27700
+ return "day";
27701
+ }
27702
+ else if (format.includes("M")) {
27703
+ return "month";
27704
+ }
27705
+ return "year";
27706
+ }
27707
+ /**
27708
+ * Returns the best time unit that should be used for the X axis of a chart in order to display all
27709
+ * the labels correctly.
27710
+ *
27711
+ * There is two conditions :
27712
+ * - the format of the labels should be able to display the unit. For example if the format is "DD/MM/YYYY"
27713
+ * it makes no sense to try to use minutes in the X axis
27714
+ * - we want the "best fit" unit. For example if the labels span a period of several days, we want to use days
27715
+ * as a unit, but if they span 200 days, we'd like to use months instead
27716
+ *
27741
27717
  */
27718
+ function getBestTimeUnitForScale(labels, format, locale) {
27719
+ const labelDates = labels.map((label) => parseDateTime(label, locale)?.jsDate);
27720
+ if (labelDates.some((date) => date === undefined) || labels.length < 2) {
27721
+ return undefined;
27722
+ }
27723
+ const labelsTimestamps = labelDates.map((date) => date.getTime());
27724
+ const period = largeMax(labelsTimestamps) - largeMin(labelsTimestamps);
27725
+ const minUnit = getFormatMinDisplayUnit(format);
27726
+ if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {
27727
+ return "second";
27728
+ }
27729
+ else if (UNIT_LENGTH.minute >= UNIT_LENGTH[minUnit] && Milliseconds.inMinutes(period) < 180) {
27730
+ return "minute";
27731
+ }
27732
+ else if (UNIT_LENGTH.hour >= UNIT_LENGTH[minUnit] && Milliseconds.inHours(period) < 96) {
27733
+ return "hour";
27734
+ }
27735
+ else if (UNIT_LENGTH.day >= UNIT_LENGTH[minUnit] && Milliseconds.inDays(period) < 90) {
27736
+ return "day";
27737
+ }
27738
+ else if (UNIT_LENGTH.month >= UNIT_LENGTH[minUnit] && Milliseconds.inMonths(period) < 36) {
27739
+ return "month";
27740
+ }
27741
+ return "year";
27742
+ }
27743
+
27744
+ function getBarChartData(definition, dataSets, labelRange, getters) {
27745
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
27746
+ let labels = labelValues.formattedValues;
27747
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
27748
+ if (definition.dataSetsHaveTitle &&
27749
+ dataSetsValues[0] &&
27750
+ labels.length > dataSetsValues[0].data.length) {
27751
+ labels.shift();
27752
+ }
27753
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
27754
+ if (definition.aggregated) {
27755
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27756
+ }
27757
+ const leftAxisFormat = getChartDatasetFormat(getters, dataSets, "left");
27758
+ const rightAxisFormat = getChartDatasetFormat(getters, dataSets, "right");
27759
+ const axisFormats = definition.horizontal
27760
+ ? { x: leftAxisFormat || rightAxisFormat }
27761
+ : { y: leftAxisFormat, y1: rightAxisFormat };
27762
+ const trendDataSetsValues = [];
27763
+ for (const index in dataSetsValues) {
27764
+ const { data } = dataSetsValues[index];
27765
+ const trend = definition.dataSets?.[index].trend;
27766
+ if (!trend?.display || definition.horizontal) {
27767
+ trendDataSetsValues.push(undefined);
27768
+ continue;
27769
+ }
27770
+ const trendDataset = getTrendDatasetForBarChart(trend, data);
27771
+ trendDataSetsValues.push(trendDataset);
27772
+ }
27773
+ return {
27774
+ dataSetsValues,
27775
+ trendDataSetsValues,
27776
+ axisFormats,
27777
+ labels,
27778
+ locale: getters.getLocale(),
27779
+ };
27780
+ }
27781
+ function getPyramidChartData(definition, dataSets, labelRange, getters) {
27782
+ const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
27783
+ const barDataset = barChartData.dataSetsValues;
27784
+ const pyramidDatasetValues = [];
27785
+ if (barDataset[0]) {
27786
+ const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
27787
+ pyramidDatasetValues.push({ ...barDataset[0], data: pyramidData });
27788
+ }
27789
+ if (barDataset[1]) {
27790
+ const pyramidData = barDataset[1].data.map((value) => (value > 0 ? -value : 0));
27791
+ pyramidDatasetValues.push({ ...barDataset[1], data: pyramidData });
27792
+ }
27793
+ return {
27794
+ ...barChartData,
27795
+ dataSetsValues: pyramidDatasetValues,
27796
+ };
27797
+ }
27798
+ function getLineChartData(definition, dataSets, labelRange, getters) {
27799
+ const axisType = getChartAxisType(definition, labelRange, getters);
27800
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
27801
+ let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
27802
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
27803
+ if (definition.dataSetsHaveTitle &&
27804
+ dataSetsValues[0] &&
27805
+ labels.length > dataSetsValues[0].data.length) {
27806
+ labels.shift();
27807
+ }
27808
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
27809
+ if (axisType === "time") {
27810
+ ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
27811
+ }
27812
+ if (definition.aggregated) {
27813
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27814
+ }
27815
+ const leftAxisFormat = getChartDatasetFormat(getters, dataSets, "left");
27816
+ const rightAxisFormat = getChartDatasetFormat(getters, dataSets, "right");
27817
+ const labelsFormat = getChartLabelFormat(getters, labelRange);
27818
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat, x: labelsFormat };
27819
+ const trendDataSetsValues = [];
27820
+ for (const index in dataSetsValues) {
27821
+ let { data } = dataSetsValues[index];
27822
+ if (definition.cumulative) {
27823
+ let accumulator = 0;
27824
+ data = data.map((value) => {
27825
+ if (!isNaN(value)) {
27826
+ accumulator += parseFloat(value);
27827
+ return accumulator;
27828
+ }
27829
+ return value;
27830
+ });
27831
+ dataSetsValues[index] = { ...dataSetsValues[index], data };
27832
+ }
27833
+ const trend = definition.dataSets?.[index].trend;
27834
+ if (!trend?.display) {
27835
+ trendDataSetsValues.push(undefined);
27836
+ continue;
27837
+ }
27838
+ trendDataSetsValues.push(getTrendDatasetForLineChart(trend, data, labels, axisType, getters.getLocale()));
27839
+ }
27840
+ return {
27841
+ dataSetsValues,
27842
+ axisFormats,
27843
+ labels,
27844
+ locale: getters.getLocale(),
27845
+ trendDataSetsValues,
27846
+ axisType,
27847
+ };
27848
+ }
27849
+ function getPieChartData(definition, dataSets, labelRange, getters) {
27850
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
27851
+ let labels = labelValues.formattedValues;
27852
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
27853
+ if (definition.dataSetsHaveTitle &&
27854
+ dataSetsValues[0] &&
27855
+ labels.length > dataSetsValues[0].data.length) {
27856
+ labels.shift();
27857
+ }
27858
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
27859
+ if (definition.aggregated) {
27860
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27861
+ }
27862
+ ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
27863
+ const dataSetFormat = getChartDatasetFormat(getters, dataSets, "left");
27864
+ return {
27865
+ dataSetsValues,
27866
+ axisFormats: { y: dataSetFormat },
27867
+ labels,
27868
+ locale: getters.getLocale(),
27869
+ };
27870
+ }
27871
+ function getRadarChartData(definition, dataSets, labelRange, getters) {
27872
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
27873
+ let labels = labelValues.formattedValues;
27874
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
27875
+ if (definition.dataSetsHaveTitle &&
27876
+ dataSetsValues[0] &&
27877
+ labels.length > dataSetsValues[0].data.length) {
27878
+ labels.shift();
27879
+ }
27880
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
27881
+ if (definition.aggregated) {
27882
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27883
+ }
27884
+ const dataSetFormat = getChartDatasetFormat(getters, dataSets, "left") ||
27885
+ getChartDatasetFormat(getters, dataSets, "right");
27886
+ const axisFormats = { r: dataSetFormat };
27887
+ return {
27888
+ dataSetsValues,
27889
+ axisFormats,
27890
+ labels,
27891
+ locale: getters.getLocale(),
27892
+ };
27893
+ }
27894
+ function getTrendDatasetForBarChart(config, data) {
27895
+ const filteredValues = [];
27896
+ const filteredLabels = [];
27897
+ const labels = [];
27898
+ for (let i = 0; i < data.length; i++) {
27899
+ if (typeof data[i] === "number") {
27900
+ filteredValues.push(data[i]);
27901
+ filteredLabels.push(i + 1);
27902
+ }
27903
+ labels.push(i + 1);
27904
+ }
27905
+ const newLabels = range(0.5, labels.length + 0.55, 0.2);
27906
+ const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
27907
+ return newValues.length ? newValues : undefined;
27908
+ }
27909
+ function getTrendDatasetForLineChart(config, data, labels, axisType, locale) {
27910
+ const filteredValues = [];
27911
+ const filteredLabels = [];
27912
+ const trendLabels = [];
27913
+ const datasetLength = data.length;
27914
+ if (datasetLength < 2) {
27915
+ return;
27916
+ }
27917
+ switch (axisType) {
27918
+ case "category":
27919
+ for (let i = 0; i < datasetLength; i++) {
27920
+ if (typeof data[i] === "number") {
27921
+ filteredValues.push(data[i]);
27922
+ filteredLabels.push(i + 1);
27923
+ }
27924
+ trendLabels.push(i + 1);
27925
+ }
27926
+ break;
27927
+ case "linear":
27928
+ for (let i = 0; i < data.length; i++) {
27929
+ const label = Number(labels[i]);
27930
+ if (isNaN(label)) {
27931
+ continue;
27932
+ }
27933
+ if (typeof data[i] === "number") {
27934
+ filteredValues.push(data[i]);
27935
+ filteredLabels.push(label);
27936
+ }
27937
+ trendLabels.push(label);
27938
+ }
27939
+ break;
27940
+ case "time":
27941
+ for (let i = 0; i < data.length; i++) {
27942
+ const date = toNumber({ value: labels[i] }, locale);
27943
+ if (data[i] !== null) {
27944
+ filteredValues.push(data[i]);
27945
+ filteredLabels.push(date);
27946
+ }
27947
+ trendLabels.push(date);
27948
+ }
27949
+ break;
27950
+ }
27951
+ const xmin = Math.min(...trendLabels);
27952
+ const xmax = Math.max(...trendLabels);
27953
+ if (xmax === xmin) {
27954
+ return;
27955
+ }
27956
+ const numberOfStep = 5 * trendLabels.length;
27957
+ const step = (xmax - xmin) / numberOfStep;
27958
+ const newLabels = range(xmin, xmax + step / 2, step);
27959
+ const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
27960
+ if (!newValues.length) {
27961
+ return;
27962
+ }
27963
+ return newValues;
27964
+ }
27965
+ function interpolateData(config, values, labels, newLabels) {
27966
+ if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
27967
+ return [];
27968
+ }
27969
+ const labelMin = Math.min(...labels);
27970
+ const labelMax = Math.max(...labels);
27971
+ const labelRange = labelMax - labelMin;
27972
+ const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
27973
+ const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
27974
+ switch (config.type) {
27975
+ case "polynomial": {
27976
+ const order = config.order ?? 2;
27977
+ if (order === 1) {
27978
+ return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
27979
+ }
27980
+ const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
27981
+ return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
27982
+ }
27983
+ case "exponential": {
27984
+ const positiveLogValues = [];
27985
+ const filteredLabels = [];
27986
+ for (let i = 0; i < values.length; i++) {
27987
+ if (values[i] > 0) {
27988
+ positiveLogValues.push(Math.log(values[i]));
27989
+ filteredLabels.push(normalizedLabels[i]);
27990
+ }
27991
+ }
27992
+ if (!filteredLabels.length) {
27993
+ return [];
27994
+ }
27995
+ return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
27996
+ }
27997
+ case "logarithmic": {
27998
+ return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
27999
+ }
28000
+ case "trailingMovingAverage": {
28001
+ return getMovingAverageValues(values, config.window);
28002
+ }
28003
+ default:
28004
+ return [];
28005
+ }
28006
+ }
28007
+ function getChartAxisType(chart, labelRange, getters) {
28008
+ if (isDateChart(chart, labelRange, getters) && isLuxonTimeAdapterInstalled()) {
28009
+ return "time";
28010
+ }
28011
+ if (isLinearChart(chart, labelRange, getters)) {
28012
+ return "linear";
28013
+ }
28014
+ return "category";
28015
+ }
28016
+ function isDateChart(definition, labelRange, getters) {
28017
+ return !definition.labelsAsText && canBeDateChart(labelRange, getters);
28018
+ }
28019
+ function isLinearChart(definition, labelRange, getters) {
28020
+ return !definition.labelsAsText && canBeLinearChart(labelRange, getters);
28021
+ }
28022
+ function canChartParseLabels(labelRange, getters) {
28023
+ return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);
28024
+ }
28025
+ function canBeDateChart(labelRange, getters) {
28026
+ if (!labelRange || !canBeLinearChart(labelRange, getters)) {
28027
+ return false;
28028
+ }
28029
+ const labelFormat = getChartLabelFormat(getters, labelRange);
28030
+ return Boolean(labelFormat && timeFormatLuxonCompatible.test(labelFormat));
28031
+ }
28032
+ function canBeLinearChart(labelRange, getters) {
28033
+ if (!labelRange) {
28034
+ return false;
28035
+ }
28036
+ const labels = getters.getRangeValues(labelRange);
28037
+ if (labels.some((label) => isNaN(Number(label)) && label)) {
28038
+ return false;
28039
+ }
28040
+ if (labels.every((label) => !label)) {
28041
+ return false;
28042
+ }
28043
+ return true;
28044
+ }
28045
+ let missingTimeAdapterAlreadyWarned = false;
28046
+ function isLuxonTimeAdapterInstalled() {
28047
+ if (!window.Chart) {
28048
+ return false;
28049
+ }
28050
+ // @ts-ignore
28051
+ const adapter = new window.Chart._adapters._date({});
28052
+ const isInstalled = adapter._id === "luxon";
28053
+ if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28054
+ missingTimeAdapterAlreadyWarned = true;
28055
+ console.warn("'chartjs-adapter-luxon' time adapter is not installed. Time scale axes are disabled.");
28056
+ }
28057
+ return isInstalled;
28058
+ }
28059
+ function filterNegativeValues(labels, datasets) {
28060
+ const dataPointsIndexes = labels.reduce((indexes, label, i) => {
28061
+ const shouldKeep = datasets.some((dataset) => {
28062
+ const dataPoint = dataset.data[i];
28063
+ return typeof dataPoint !== "number" || dataPoint >= 0;
28064
+ });
28065
+ if (shouldKeep) {
28066
+ indexes.push(i);
28067
+ }
28068
+ return indexes;
28069
+ }, []);
28070
+ const filteredLabels = dataPointsIndexes.map((i) => labels[i] || "");
28071
+ const filteredDatasets = datasets.map((dataset) => ({
28072
+ ...dataset,
28073
+ data: dataPointsIndexes.map((i) => {
28074
+ const dataPoint = dataset.data[i];
28075
+ return typeof dataPoint !== "number" || dataPoint >= 0 ? dataPoint : 0;
28076
+ }),
28077
+ }));
28078
+ return { labels: filteredLabels, dataSetsValues: filteredDatasets };
28079
+ }
28080
+ function fixEmptyLabelsForDateCharts(labels, dataSetsValues) {
28081
+ if (labels.length === 0 || labels.every((label) => !label)) {
28082
+ return { labels, dataSetsValues };
28083
+ }
28084
+ const newLabels = [...labels];
28085
+ const newDatasets = deepCopy(dataSetsValues);
28086
+ for (let i = 0; i < newLabels.length; i++) {
28087
+ if (!newLabels[i]) {
28088
+ newLabels[i] = findNextDefinedValue(newLabels, i);
28089
+ for (let ds of newDatasets) {
28090
+ ds.data[i] = undefined;
28091
+ }
28092
+ }
28093
+ }
28094
+ return { labels: newLabels, dataSetsValues: newDatasets };
28095
+ }
27742
28096
  /**
27743
28097
  * Get the data from a dataSet
27744
28098
  */
@@ -27793,96 +28147,17 @@ function aggregateDataForLabels(labels, datasets) {
27793
28147
  })),
27794
28148
  };
27795
28149
  }
27796
- function truncateLabel(label) {
27797
- if (!label) {
27798
- return "";
27799
- }
27800
- if (label.length > MAX_CHAR_LABEL) {
27801
- return label.substring(0, MAX_CHAR_LABEL) + "…";
27802
- }
27803
- return label;
27804
- }
27805
- /**
27806
- * Get a default chart js configuration
27807
- */
27808
- function getDefaultChartJsRuntime(chart, labels, fontColor, { axisFormats, locale, truncateLabels = true, horizontalChart }) {
27809
- const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
27810
- const chartOptions = {
27811
- // https://www.chartjs.org/docs/latest/general/responsive.html
27812
- responsive: true, // will resize when its container is resized
27813
- maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
27814
- layout: {
27815
- padding: {
27816
- left: DEFAULT_CHART_PADDING,
27817
- right: DEFAULT_CHART_PADDING,
27818
- top: chartTitle.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
27819
- bottom: DEFAULT_CHART_PADDING,
27820
- },
27821
- },
27822
- elements: {
27823
- line: {
27824
- fill: false, // do not fill the area under line charts
27825
- },
27826
- point: {
27827
- hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
27828
- },
27829
- },
27830
- animation: false,
27831
- plugins: {
27832
- title: {
27833
- display: !!chartTitle.text,
27834
- text: _t(chartTitle.text),
27835
- color: chartTitle?.color ?? fontColor,
27836
- align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
27837
- font: {
27838
- size: DEFAULT_CHART_FONT_SIZE,
27839
- weight: chartTitle.bold ? "bold" : "normal",
27840
- style: chartTitle.italic ? "italic" : "normal",
27841
- },
27842
- },
27843
- legend: {
27844
- // Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.
27845
- // If we want to re-enable this in the future, we need to override the default onClick to stop the event propagation
27846
- onClick: () => { },
27847
- },
27848
- tooltip: {
27849
- callbacks: {
27850
- label: function (tooltipItem) {
27851
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
27852
- // tooltipItem.parsed can be an object or a number for pie charts
27853
- let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
27854
- if (yLabel === undefined || yLabel === null) {
27855
- yLabel = tooltipItem.parsed;
27856
- }
27857
- const axisId = horizontalChart
27858
- ? tooltipItem.dataset.xAxisID
27859
- : tooltipItem.dataset.yAxisID;
27860
- const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
27861
- return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
27862
- },
27863
- },
27864
- },
27865
- },
27866
- };
27867
- return {
27868
- type: chart.type,
27869
- options: chartOptions,
27870
- data: {
27871
- labels: truncateLabels ? labels.map(truncateLabel) : labels,
27872
- datasets: [],
27873
- },
27874
- platform: undefined, // This key is optional and will be set by chart.js
27875
- plugins: [],
27876
- };
27877
- }
27878
28150
  function getChartLabelFormat(getters, range) {
27879
28151
  if (!range)
27880
28152
  return undefined;
27881
- return getters.getEvaluatedCell({
27882
- sheetId: range.sheetId,
27883
- col: range.zone.left,
27884
- row: range.zone.top,
27885
- }).format;
28153
+ const { sheetId, zone: { left, top, bottom }, } = range;
28154
+ for (let row = top; row <= bottom; row++) {
28155
+ const format = getters.getEvaluatedCell({ sheetId, col: left, row }).format;
28156
+ if (format) {
28157
+ return format;
28158
+ }
28159
+ }
28160
+ return undefined;
27886
28161
  }
27887
28162
  function getChartLabelValues(getters, dataSets, labelRange) {
27888
28163
  let labels = { values: [], formattedValues: [] };
@@ -27974,6 +28249,218 @@ function getChartDatasetValues(getters, dataSets) {
27974
28249
  }
27975
28250
  return datasetValues;
27976
28251
  }
28252
+
28253
+ function getBarChartDatasets(definition, args) {
28254
+ const { dataSetsValues } = args;
28255
+ const dataSets = [];
28256
+ const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28257
+ const trendDatasets = [];
28258
+ for (const index in dataSetsValues) {
28259
+ let { label, data } = dataSetsValues[index];
28260
+ label = definition.dataSets?.[index].label || label;
28261
+ const backgroundColor = colors.next();
28262
+ const dataset = {
28263
+ label,
28264
+ data,
28265
+ borderColor: BORDER_CHART_COLOR,
28266
+ borderWidth: 1,
28267
+ backgroundColor,
28268
+ yAxisID: definition.horizontal ? "y" : definition.dataSets?.[index].yAxisId || "y",
28269
+ xAxisID: "x",
28270
+ };
28271
+ dataSets.push(dataset);
28272
+ const trendConfig = definition.dataSets?.[index].trend;
28273
+ const trendData = args.trendDataSetsValues?.[index];
28274
+ if (!trendConfig?.display || definition.horizontal || !trendData) {
28275
+ continue;
28276
+ }
28277
+ trendDatasets.push(getTrendingLineDataSet(dataset, trendConfig, trendData));
28278
+ }
28279
+ dataSets.push(...trendDatasets);
28280
+ return dataSets;
28281
+ }
28282
+ function getWaterfallDatasetAndLabels(definition, args) {
28283
+ const { dataSetsValues, labels } = args;
28284
+ const negativeColor = definition.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
28285
+ const positiveColor = definition.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
28286
+ const subTotalColor = definition.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
28287
+ const backgroundColor = [];
28288
+ const datasetValues = [];
28289
+ const dataset = {
28290
+ label: "",
28291
+ data: datasetValues,
28292
+ backgroundColor,
28293
+ };
28294
+ const labelsWithSubTotals = [];
28295
+ let lastValue = 0;
28296
+ for (const dataSetsValue of dataSetsValues) {
28297
+ for (let i = 0; i < dataSetsValue.data.length; i++) {
28298
+ const data = dataSetsValue.data[i];
28299
+ labelsWithSubTotals.push(labels[i]);
28300
+ if (isNaN(Number(data))) {
28301
+ datasetValues.push([lastValue, lastValue]);
28302
+ backgroundColor.push("");
28303
+ continue;
28304
+ }
28305
+ datasetValues.push([lastValue, data + lastValue]);
28306
+ let color = data >= 0 ? positiveColor : negativeColor;
28307
+ if (i === 0 && dataSetsValue === dataSetsValues[0] && definition.firstValueAsSubtotal) {
28308
+ color = subTotalColor;
28309
+ }
28310
+ backgroundColor.push(color);
28311
+ lastValue += data;
28312
+ }
28313
+ if (definition.showSubTotals) {
28314
+ labelsWithSubTotals.push(_t("Subtotal"));
28315
+ datasetValues.push([0, lastValue]);
28316
+ backgroundColor.push(subTotalColor);
28317
+ }
28318
+ }
28319
+ return {
28320
+ datasets: [dataset],
28321
+ labels: labelsWithSubTotals.map(truncateLabel),
28322
+ };
28323
+ }
28324
+ function getLineChartDatasets(definition, args) {
28325
+ const { dataSetsValues, axisType, labels } = args;
28326
+ const dataSets = [];
28327
+ const areaChart = !!definition.fillArea;
28328
+ const stackedChart = !!definition.stacked;
28329
+ const trendDatasets = [];
28330
+ const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28331
+ for (let index = 0; index < dataSetsValues.length; index++) {
28332
+ let { label, data } = dataSetsValues[index];
28333
+ label = definition.dataSets?.[index].label || label;
28334
+ const color = colors.next();
28335
+ if (axisType && ["linear", "time"].includes(axisType)) {
28336
+ // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
28337
+ data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
28338
+ }
28339
+ const dataset = {
28340
+ label,
28341
+ data,
28342
+ tension: 0, // 0 -> render straight lines, which is much faster
28343
+ borderColor: color,
28344
+ backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
28345
+ pointBackgroundColor: color,
28346
+ fill: areaChart ? getFillingMode(index, stackedChart) : false,
28347
+ yAxisID: definition.dataSets?.[index].yAxisId || "y",
28348
+ };
28349
+ dataSets.push(dataset);
28350
+ const trendConfig = definition.dataSets?.[index].trend;
28351
+ const trendData = args.trendDataSetsValues?.[index];
28352
+ if (!trendConfig?.display || !trendData) {
28353
+ continue;
28354
+ }
28355
+ trendDatasets.push(getTrendingLineDataSet(dataset, trendConfig, trendData));
28356
+ }
28357
+ dataSets.push(...trendDatasets);
28358
+ return dataSets;
28359
+ }
28360
+ function getScatterChartDatasets(definition, args) {
28361
+ const dataSets = getLineChartDatasets(definition, args);
28362
+ for (const dataSet of dataSets) {
28363
+ if (dataSet.xAxisID !== TREND_LINE_XAXIS_ID) {
28364
+ dataSet.showLine = false;
28365
+ }
28366
+ }
28367
+ return dataSets;
28368
+ }
28369
+ function getPieChartDatasets(definition, args) {
28370
+ const { dataSetsValues } = args;
28371
+ const dataSets = [];
28372
+ const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
28373
+ const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
28374
+ for (const { label, data } of dataSetsValues) {
28375
+ const dataset = {
28376
+ label,
28377
+ data,
28378
+ borderColor: BACKGROUND_CHART_COLOR,
28379
+ backgroundColor,
28380
+ hoverOffset: 30,
28381
+ };
28382
+ dataSets.push(dataset);
28383
+ }
28384
+ return dataSets;
28385
+ }
28386
+ function getComboChartDatasets(definition, args) {
28387
+ const { dataSetsValues } = args;
28388
+ const dataSets = [];
28389
+ const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28390
+ const trendDatasets = [];
28391
+ for (let index = 0; index < dataSetsValues.length; index++) {
28392
+ let { label, data } = dataSetsValues[index];
28393
+ label = definition.dataSets?.[index].label || label;
28394
+ const design = definition.dataSets?.[index];
28395
+ const color = colors.next();
28396
+ const type = design?.type ?? "line";
28397
+ const dataset = {
28398
+ label: label,
28399
+ data,
28400
+ borderColor: color,
28401
+ backgroundColor: color,
28402
+ yAxisID: definition.dataSets?.[index].yAxisId || "y",
28403
+ xAxisID: "x",
28404
+ type,
28405
+ order: type === "bar" ? dataSetsValues.length + index : index,
28406
+ };
28407
+ dataSets.push(dataset);
28408
+ const trendConfig = definition.dataSets?.[index].trend;
28409
+ const trendData = args.trendDataSetsValues?.[index];
28410
+ if (!trendConfig?.display || !trendData) {
28411
+ continue;
28412
+ }
28413
+ trendDatasets.push(getTrendingLineDataSet(dataset, trendConfig, trendData));
28414
+ }
28415
+ dataSets.push(...trendDatasets);
28416
+ return dataSets;
28417
+ }
28418
+ function getRadarChartDatasets(definition, args) {
28419
+ const { dataSetsValues } = args;
28420
+ const datasets = [];
28421
+ const fill = definition.fillArea ?? false;
28422
+ const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28423
+ for (let i = 0; i < dataSetsValues.length; i++) {
28424
+ let { label, data } = dataSetsValues[i];
28425
+ if (definition.dataSets?.[i]?.label) {
28426
+ label = definition.dataSets[i].label;
28427
+ }
28428
+ const borderColor = colors.next();
28429
+ const dataset = {
28430
+ label,
28431
+ data,
28432
+ borderColor,
28433
+ backgroundColor: borderColor,
28434
+ };
28435
+ if (fill) {
28436
+ dataset.backgroundColor = setColorAlpha(borderColor, LINE_FILL_TRANSPARENCY);
28437
+ dataset.fill = "start"; // fills from the start of the axes (default is to start at 0)
28438
+ }
28439
+ datasets.push(dataset);
28440
+ }
28441
+ return datasets;
28442
+ }
28443
+ function getTrendingLineDataSet(dataset, config, data) {
28444
+ const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
28445
+ defaultBorderColor.a = 1;
28446
+ const borderColor = config.color || lightenColor(rgbaToHex(defaultBorderColor), 0.5);
28447
+ return {
28448
+ type: "line",
28449
+ xAxisID: TREND_LINE_XAXIS_ID,
28450
+ yAxisID: dataset.yAxisID,
28451
+ label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
28452
+ data,
28453
+ order: -1,
28454
+ showLine: true,
28455
+ pointRadius: 0,
28456
+ backgroundColor: borderColor,
28457
+ borderColor,
28458
+ borderDash: [5, 5],
28459
+ borderWidth: undefined,
28460
+ fill: false,
28461
+ pointBackgroundColor: borderColor,
28462
+ };
28463
+ }
27977
28464
  /**
27978
28465
  * If the chart is a stacked area chart, we want to fill until the next dataset.
27979
28466
  * If the chart is a simple area chart, we want to fill until the origin (bottom axis).
@@ -27986,57 +28473,639 @@ function getFillingMode(index, stackedChart) {
27986
28473
  }
27987
28474
  return index === 0 ? "origin" : "-1";
27988
28475
  }
27989
- function chartToImage(runtime, figure, type) {
27990
- // wrap the canvas in a div with a fixed size because chart.js would
27991
- // fill the whole page otherwise
27992
- const div = document.createElement("div");
27993
- div.style.width = `${figure.width}px`;
27994
- div.style.height = `${figure.height}px`;
27995
- const canvas = document.createElement("canvas");
27996
- div.append(canvas);
27997
- canvas.setAttribute("width", figure.width.toString());
27998
- canvas.setAttribute("height", figure.height.toString());
27999
- // we have to add the canvas to the DOM otherwise it won't be rendered
28000
- document.body.append(div);
28001
- if ("chartJsConfig" in runtime) {
28002
- const config = deepCopy(runtime.chartJsConfig);
28003
- config.plugins = [backgroundColorChartJSPlugin];
28004
- const chart = new window.Chart(canvas, config);
28005
- const imgContent = chart.toBase64Image();
28006
- chart.destroy();
28007
- div.remove();
28008
- return imgContent;
28009
- }
28010
- else if (type === "scorecard") {
28011
- const design = getScorecardConfiguration(figure, runtime);
28012
- drawScoreChart(design, canvas);
28013
- const imgContent = canvas.toDataURL();
28014
- div.remove();
28015
- return imgContent;
28016
- }
28017
- else if (type === "gauge") {
28018
- drawGaugeChart(canvas, runtime);
28019
- const imgContent = canvas.toDataURL();
28020
- div.remove();
28021
- return imgContent;
28476
+ function getChartColorsGenerator(definition, dataSetsSize) {
28477
+ return new ColorGenerator(dataSetsSize, definition.dataSets?.map((ds) => ds.backgroundColor) || []);
28478
+ }
28479
+
28480
+ function getCommonChartLayout(definition) {
28481
+ // TODO FIXME: this is unused ATM. All the charts should probably use this instead oh whatever padding they are using now
28482
+ // also look into how DEFAULT_CHART_PADDING is used in scorecards, it look strange
28483
+ return {
28484
+ padding: {
28485
+ left: DEFAULT_CHART_PADDING,
28486
+ right: DEFAULT_CHART_PADDING,
28487
+ top: definition.title?.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
28488
+ bottom: DEFAULT_CHART_PADDING,
28489
+ },
28490
+ };
28491
+ }
28492
+ function getBarChartLayout(definition) {
28493
+ return {
28494
+ padding: computeChartPadding({
28495
+ displayTitle: !!definition.title?.text,
28496
+ displayLegend: definition.legendPosition === "top",
28497
+ }),
28498
+ };
28499
+ }
28500
+ function getLineChartLayout(definition) {
28501
+ return {
28502
+ padding: computeChartPadding({
28503
+ displayTitle: !!definition.title?.text,
28504
+ displayLegend: definition.legendPosition === "top",
28505
+ }),
28506
+ };
28507
+ }
28508
+ function getPieChartLayout(definition) {
28509
+ return {
28510
+ padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28511
+ };
28512
+ }
28513
+ function getWaterfallChartLayout(definition) {
28514
+ return {
28515
+ padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28516
+ };
28517
+ }
28518
+ function computeChartPadding({ displayTitle, displayLegend, }) {
28519
+ let top = 25;
28520
+ if (displayTitle) {
28521
+ top = 0;
28522
+ }
28523
+ else if (displayLegend) {
28524
+ top = 10;
28525
+ }
28526
+ return { left: 20, right: 20, top, bottom: 10 };
28527
+ }
28528
+
28529
+ function getLegendDisplayOptions(definition, args) {
28530
+ return {
28531
+ display: definition.legendPosition !== "none",
28532
+ position: definition.legendPosition !== "none" ? definition.legendPosition : undefined,
28533
+ };
28534
+ }
28535
+ function getBarChartLegend(definition, args) {
28536
+ return {
28537
+ ...INTERACTIVE_LEGEND_CONFIG,
28538
+ ...getLegendDisplayOptions(definition),
28539
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
28540
+ pointStyle: "rect",
28541
+ lineWidth: 3,
28542
+ }),
28543
+ };
28544
+ }
28545
+ function getLineChartLegend(definition, args) {
28546
+ const filled = definition.fillArea;
28547
+ const pointStyle = filled ? "rect" : "line";
28548
+ const lineWidth = filled ? 2 : 3;
28549
+ return {
28550
+ ...INTERACTIVE_LEGEND_CONFIG,
28551
+ ...getLegendDisplayOptions(definition),
28552
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
28553
+ pointStyle,
28554
+ lineWidth,
28555
+ }),
28556
+ };
28557
+ }
28558
+ function getPieChartLegend(definition, args) {
28559
+ const { dataSetsValues } = args;
28560
+ const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
28561
+ const colors = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
28562
+ return {
28563
+ ...getLegendDisplayOptions(definition),
28564
+ labels: {
28565
+ color: chartFontColor(definition.background),
28566
+ usePointStyle: true,
28567
+ //@ts-ignore
28568
+ generateLabels: (c) =>
28569
+ //@ts-ignore
28570
+ c.data.labels.map((label, index) => ({
28571
+ text: label,
28572
+ strokeStyle: colors[index],
28573
+ fillStyle: colors[index],
28574
+ pointStyle: "rect",
28575
+ hidden: false,
28576
+ lineWidth: 2,
28577
+ })),
28578
+ },
28579
+ };
28580
+ }
28581
+ function getScatterChartLegend(definition, args) {
28582
+ return {
28583
+ ...INTERACTIVE_LEGEND_CONFIG,
28584
+ ...getLegendDisplayOptions(definition),
28585
+ labels: {
28586
+ color: chartFontColor(definition.background),
28587
+ boxHeight: 6,
28588
+ usePointStyle: true,
28589
+ },
28590
+ };
28591
+ }
28592
+ function getComboChartLegend(definition, args) {
28593
+ return {
28594
+ ...INTERACTIVE_LEGEND_CONFIG,
28595
+ ...getLegendDisplayOptions(definition),
28596
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
28597
+ lineWidth: 3,
28598
+ }),
28599
+ };
28600
+ }
28601
+ function getWaterfallChartLegend(definition, args) {
28602
+ const fontColor = chartFontColor(definition.background);
28603
+ const negativeColor = definition.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
28604
+ const positiveColor = definition.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
28605
+ const subTotalColor = definition.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
28606
+ return {
28607
+ ...getLegendDisplayOptions(definition),
28608
+ labels: {
28609
+ usePointStyle: true,
28610
+ generateLabels: () => {
28611
+ const legendValues = [
28612
+ {
28613
+ text: _t("Positive values"),
28614
+ fontColor,
28615
+ fillStyle: positiveColor,
28616
+ strokeStyle: positiveColor,
28617
+ pointStyle: "rect",
28618
+ },
28619
+ {
28620
+ text: _t("Negative values"),
28621
+ fontColor,
28622
+ fillStyle: negativeColor,
28623
+ strokeStyle: negativeColor,
28624
+ pointStyle: "rect",
28625
+ },
28626
+ ];
28627
+ if (definition.showSubTotals || definition.firstValueAsSubtotal) {
28628
+ legendValues.push({
28629
+ text: _t("Subtotals"),
28630
+ fontColor,
28631
+ fillStyle: subTotalColor,
28632
+ strokeStyle: subTotalColor,
28633
+ pointStyle: "rect",
28634
+ });
28635
+ }
28636
+ return legendValues;
28637
+ },
28638
+ },
28639
+ };
28640
+ }
28641
+ function getRadarChartLegend(definition, args) {
28642
+ const fill = definition.fillArea ?? false;
28643
+ const pointStyle = fill ? "rect" : "line";
28644
+ const lineWidth = fill ? 2 : 3;
28645
+ return {
28646
+ ...INTERACTIVE_LEGEND_CONFIG,
28647
+ ...getLegendDisplayOptions(definition),
28648
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
28649
+ pointStyle,
28650
+ lineWidth,
28651
+ }),
28652
+ };
28653
+ }
28654
+ /* Callback used to make the legend interactive
28655
+ * These are used to make the user able to hide/show a data series by
28656
+ * clicking on the corresponding label in the legend. The onHover and
28657
+ * onLeave callbacks are used to show a pointer when hovering an item
28658
+ * of the legend so that the user knows it is clickable.
28659
+ */
28660
+ const INTERACTIVE_LEGEND_CONFIG = {
28661
+ onHover: (event) => {
28662
+ const target = event.native?.target;
28663
+ if (!target) {
28664
+ return;
28665
+ }
28666
+ //@ts-ignore
28667
+ target.style.cursor = "pointer";
28668
+ },
28669
+ onLeave: (event) => {
28670
+ const target = event.native?.target;
28671
+ if (!target) {
28672
+ return;
28673
+ }
28674
+ //@ts-ignore
28675
+ target.style.cursor = "default";
28676
+ },
28677
+ onClick: (event, legendItem, legend) => {
28678
+ if (!legend.legendItems) {
28679
+ return;
28680
+ }
28681
+ const index = legend.legendItems.indexOf(legendItem);
28682
+ if (legend.chart.isDatasetVisible(index)) {
28683
+ legend.chart.hide(index);
28684
+ }
28685
+ else {
28686
+ legend.chart.show(index);
28687
+ }
28688
+ event.native.preventDefault();
28689
+ event.native.stopPropagation();
28690
+ },
28691
+ };
28692
+ function getCustomLegendLabels(fontColor, legendLabelConfig) {
28693
+ return {
28694
+ labels: {
28695
+ color: fontColor,
28696
+ usePointStyle: true,
28697
+ generateLabels: (chart) => chart.data.datasets.map((dataset, index) => ({
28698
+ text: dataset.label ?? "",
28699
+ fontColor,
28700
+ strokeStyle: dataset.borderColor,
28701
+ fillStyle: dataset.backgroundColor,
28702
+ hidden: !chart.isDatasetVisible(index),
28703
+ pointStyle: dataset.type === "line" ? "line" : "rect",
28704
+ ...legendLabelConfig,
28705
+ })),
28706
+ },
28707
+ };
28708
+ }
28709
+
28710
+ function getBarChartScales(definition, args) {
28711
+ let scales = {};
28712
+ const { trendDataSetsValues: trendDatasets, locale, axisFormats } = args;
28713
+ const options = { stacked: definition.stacked, locale: locale };
28714
+ if (definition.horizontal) {
28715
+ scales.x = getChartAxis(definition, "bottom", "values", { ...options, format: axisFormats?.x });
28716
+ scales.y = getChartAxis(definition, "left", "labels", options);
28717
+ }
28718
+ else {
28719
+ scales.x = getChartAxis(definition, "bottom", "labels", options);
28720
+ const leftAxisOptions = { ...options, format: axisFormats?.y };
28721
+ scales.y = getChartAxis(definition, "left", "values", leftAxisOptions);
28722
+ const rightAxisOptions = { ...options, format: axisFormats?.y1 };
28723
+ scales.y1 = getChartAxis(definition, "right", "values", rightAxisOptions);
28724
+ }
28725
+ scales = removeFalsyAttributes(scales);
28726
+ if (trendDatasets && trendDatasets.length && trendDatasets.some(isDefined)) {
28727
+ /* We add a second x axis here to draw the trend lines, with the labels length being
28728
+ * set so that the second axis points match the classical x axis
28729
+ */
28730
+ const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset?.length || 0));
28731
+ scales[TREND_LINE_XAXIS_ID] = {
28732
+ ...scales.x,
28733
+ labels: Array(maxLength).fill(""),
28734
+ offset: false,
28735
+ display: false,
28736
+ };
28737
+ }
28738
+ return scales;
28739
+ }
28740
+ function getLineChartScales(definition, args) {
28741
+ const { locale, axisType, trendDataSetsValues: trendDatasets, labels, axisFormats } = args;
28742
+ const labelFormat = axisFormats?.x;
28743
+ const stacked = definition.stacked;
28744
+ let scales = {
28745
+ x: getChartAxis(definition, "bottom", "labels", { locale }),
28746
+ y: getChartAxis(definition, "left", "values", { locale, stacked, format: axisFormats?.y }),
28747
+ y1: getChartAxis(definition, "right", "values", { locale, stacked, format: axisFormats?.y1 }),
28748
+ };
28749
+ scales = removeFalsyAttributes(scales);
28750
+ if (axisType === "time" && labels && labelFormat) {
28751
+ const axis = {
28752
+ type: "time",
28753
+ time: getChartTimeOptions(labels, labelFormat, locale),
28754
+ };
28755
+ Object.assign(scales.x, axis);
28756
+ scales.x.ticks.maxTicksLimit = 15;
28757
+ }
28758
+ else if (axisType === "linear") {
28759
+ scales.x.type = "linear";
28760
+ scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
28761
+ }
28762
+ if (trendDatasets && trendDatasets.length && trendDatasets.some(isDefined)) {
28763
+ /* We add a second x axis here to draw the trend lines, with the labels length being
28764
+ * set so that the second axis points match the classical x axis
28765
+ */
28766
+ const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset?.length || 0));
28767
+ scales[TREND_LINE_XAXIS_ID] = {
28768
+ ...scales.x,
28769
+ type: "category",
28770
+ labels: range(0, maxLength).map((x) => x.toString()),
28771
+ offset: false,
28772
+ display: false,
28773
+ };
28774
+ }
28775
+ return scales;
28776
+ }
28777
+ function getScatterChartScales(definition, args) {
28778
+ const lineScales = getLineChartScales(definition, args);
28779
+ return {
28780
+ ...lineScales,
28781
+ x: {
28782
+ ...lineScales.x,
28783
+ grid: { display: true },
28784
+ },
28785
+ };
28786
+ }
28787
+ function getWaterfallChartScales(definition, args) {
28788
+ const { locale, axisFormats } = args;
28789
+ const format = axisFormats?.y || axisFormats?.y1;
28790
+ definition.dataSets;
28791
+ const scales = {
28792
+ x: {
28793
+ ...getChartAxis(definition, "bottom", "labels", { locale }),
28794
+ grid: { display: false },
28795
+ },
28796
+ y: {
28797
+ // TODO FIXME: we should probably remove definition.verticalAxisPosition and put everything inside axesDesign/datasets
28798
+ // like the other charts. We cannot use helpers like `getChartAxis` here because they look into definition.dataSet
28799
+ // which have plain wrong information, eg. the yAxisId of the dataset being "y" when the data is actually displayed
28800
+ // on the axis to the right.
28801
+ position: definition.verticalAxisPosition,
28802
+ ticks: {
28803
+ color: chartFontColor(definition.background),
28804
+ callback: formatTickValue({ locale, format }),
28805
+ },
28806
+ grid: {
28807
+ lineWidth: (context) => (context.tick.value === 0 ? 2 : 1),
28808
+ },
28809
+ title: getChartAxisTitleRuntime(definition.axesDesign?.y),
28810
+ },
28811
+ };
28812
+ const verticalScale = scales?.y || scales?.y1;
28813
+ if (verticalScale) {
28814
+ verticalScale.grid = { lineWidth: (context) => (context.tick.value === 0 ? 2 : 1) };
28815
+ }
28816
+ return scales;
28817
+ }
28818
+ function getPyramidChartScales(definition, args) {
28819
+ const scales = getBarChartScales(definition, args);
28820
+ const scalesXCallback = scales.x.ticks.callback;
28821
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
28822
+ return scales;
28823
+ }
28824
+ function getRadarChartScales(definition, args) {
28825
+ const { locale, axisFormats } = args;
28826
+ return {
28827
+ r: {
28828
+ ticks: {
28829
+ callback: formatTickValue({ format: axisFormats?.r, locale }),
28830
+ backdropColor: definition.background || "#FFFFFF",
28831
+ },
28832
+ pointLabels: { color: chartFontColor(definition.background) },
28833
+ },
28834
+ };
28835
+ }
28836
+ function getChartAxisTitleRuntime(design) {
28837
+ if (design?.title?.text) {
28838
+ const { text, color, align, italic, bold } = design.title;
28839
+ return {
28840
+ display: true,
28841
+ text,
28842
+ color,
28843
+ font: {
28844
+ style: italic ? "italic" : "normal",
28845
+ weight: bold ? "bold" : "normal",
28846
+ },
28847
+ align: align === "left" ? "start" : align === "right" ? "end" : "center",
28848
+ };
28849
+ }
28850
+ return;
28851
+ }
28852
+ function getChartAxis(definition, position, type, options) {
28853
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
28854
+ if ((position === "left" && !useLeftAxis) || (position === "right" && !useRightAxis)) {
28855
+ return undefined;
28856
+ }
28857
+ const fontColor = chartFontColor(definition.background);
28858
+ let design;
28859
+ if (position === "bottom") {
28860
+ design = definition.axesDesign?.x;
28861
+ }
28862
+ else if (position === "left") {
28863
+ design = definition.axesDesign?.y;
28864
+ }
28865
+ else {
28866
+ design = definition.axesDesign?.y1;
28867
+ }
28868
+ if (type === "values") {
28869
+ const displayGridLines = !(position === "right" && useLeftAxis);
28870
+ return {
28871
+ position: position,
28872
+ title: getChartAxisTitleRuntime(design),
28873
+ grid: {
28874
+ display: displayGridLines,
28875
+ },
28876
+ beginAtZero: true,
28877
+ stacked: options?.stacked,
28878
+ ticks: {
28879
+ color: fontColor,
28880
+ callback: formatTickValue(options),
28881
+ },
28882
+ };
28883
+ }
28884
+ else {
28885
+ return {
28886
+ ticks: {
28887
+ padding: 5,
28888
+ color: fontColor,
28889
+ },
28890
+ grid: {
28891
+ display: false,
28892
+ },
28893
+ stacked: options?.stacked,
28894
+ title: getChartAxisTitleRuntime(design),
28895
+ };
28896
+ }
28897
+ }
28898
+
28899
+ function getChartShowValues(definition, args) {
28900
+ const { axisFormats, locale } = args;
28901
+ return {
28902
+ horizontal: "horizontal" in definition && definition.horizontal,
28903
+ showValues: "showValues" in definition ? !!definition.showValues : false,
28904
+ background: definition.background,
28905
+ callback: formatChartDatasetValue(axisFormats, locale),
28906
+ };
28907
+ }
28908
+
28909
+ function getChartTitle(definition) {
28910
+ const chartTitle = definition.title;
28911
+ const fontColor = chartFontColor(definition.background);
28912
+ return {
28913
+ display: !!chartTitle.text,
28914
+ text: _t(chartTitle.text),
28915
+ color: chartTitle?.color ?? fontColor,
28916
+ align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
28917
+ font: {
28918
+ size: DEFAULT_CHART_FONT_SIZE,
28919
+ weight: chartTitle.bold ? "bold" : "normal",
28920
+ style: chartTitle.italic ? "italic" : "normal",
28921
+ },
28922
+ };
28923
+ }
28924
+
28925
+ function getBarChartTooltip(definition, args) {
28926
+ return {
28927
+ callbacks: {
28928
+ title: function (tooltipItems) {
28929
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28930
+ ? undefined
28931
+ : "";
28932
+ },
28933
+ label: function (tooltipItem) {
28934
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
28935
+ const horizontalChart = definition.horizontal;
28936
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
28937
+ if (yLabel === undefined || yLabel === null) {
28938
+ yLabel = tooltipItem.parsed;
28939
+ }
28940
+ const axisId = horizontalChart ? tooltipItem.dataset.xAxisID : tooltipItem.dataset.yAxisID;
28941
+ const yLabelStr = formatChartDatasetValue(args.axisFormats, args.locale)(yLabel, axisId);
28942
+ return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
28943
+ },
28944
+ },
28945
+ };
28946
+ }
28947
+ function getLineChartTooltip(definition, args) {
28948
+ const { axisType, locale, axisFormats } = args;
28949
+ const labelFormat = axisFormats?.x;
28950
+ const tooltip = { callbacks: {} };
28951
+ if (axisType === "linear") {
28952
+ tooltip.callbacks.label = (tooltipItem) => {
28953
+ const dataSetPoint = tooltipItem.parsed.y;
28954
+ let label = tooltipItem.parsed.x;
28955
+ if (typeof label === "string" && isNumber(label, locale)) {
28956
+ label = toNumber(label, locale);
28957
+ }
28958
+ const formattedX = formatValue(label, { locale, format: labelFormat });
28959
+ const axisId = tooltipItem.dataset.yAxisID || "y";
28960
+ const formattedY = formatValue(dataSetPoint, { locale, format: axisFormats?.[axisId] });
28961
+ const dataSetTitle = tooltipItem.dataset.label;
28962
+ return formattedX
28963
+ ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
28964
+ : `${dataSetTitle}: ${formattedY}`;
28965
+ };
28966
+ }
28967
+ else {
28968
+ tooltip.callbacks.label = function (tooltipItem) {
28969
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
28970
+ const yLabel = tooltipItem.parsed.y;
28971
+ const axisId = tooltipItem.dataset.yAxisID;
28972
+ const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
28973
+ return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
28974
+ };
28975
+ }
28976
+ tooltip.callbacks.title = function (tooltipItems) {
28977
+ const displayTooltipTitle = axisType !== "linear" &&
28978
+ tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
28979
+ return displayTooltipTitle ? undefined : "";
28980
+ };
28981
+ return tooltip;
28982
+ }
28983
+ function getPieChartTooltip(definition, args) {
28984
+ const { locale, axisFormats } = args;
28985
+ const format = axisFormats?.y || axisFormats?.y1;
28986
+ return {
28987
+ callbacks: {
28988
+ title: function (tooltipItems) {
28989
+ return tooltipItems[0].dataset.label;
28990
+ },
28991
+ label: function (tooltipItem) {
28992
+ const data = tooltipItem.dataset.data;
28993
+ const dataIndex = tooltipItem.dataIndex;
28994
+ const percentage = calculatePercentage(data, dataIndex);
28995
+ const xLabel = tooltipItem.label || tooltipItem.dataset.label;
28996
+ const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
28997
+ const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
28998
+ const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
28999
+ return xLabel
29000
+ ? `${xLabel}: ${yLabelStr} (${percentage}%)`
29001
+ : `${yLabelStr} (${percentage}%)`;
29002
+ },
29003
+ },
29004
+ };
29005
+ }
29006
+ function getWaterfallChartTooltip(definition, args) {
29007
+ const { dataSetsValues, locale, axisFormats, labels } = args;
29008
+ const format = axisFormats?.y || axisFormats?.y1;
29009
+ const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
29010
+ return {
29011
+ callbacks: {
29012
+ label: function (tooltipItem) {
29013
+ const [lastValue, currentValue] = tooltipItem.raw;
29014
+ const yLabel = currentValue - lastValue;
29015
+ const dataSeriesIndex = labels.length
29016
+ ? Math.floor(tooltipItem.dataIndex / labels.length)
29017
+ : 0;
29018
+ const dataSeriesLabel = dataSeriesLabels[dataSeriesIndex];
29019
+ const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
29020
+ const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29021
+ return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
29022
+ },
29023
+ },
29024
+ };
29025
+ }
29026
+ function getPyramidChartTooltip(definition, args) {
29027
+ const tooltip = getBarChartTooltip(definition, args);
29028
+ return {
29029
+ ...tooltip,
29030
+ callbacks: {
29031
+ ...tooltip.callbacks,
29032
+ label: (item) => {
29033
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
29034
+ return (tooltip?.callbacks?.label)(tooltipItem);
29035
+ },
29036
+ },
29037
+ };
29038
+ }
29039
+ function getRadarChartTooltip(definition, args) {
29040
+ const { locale, axisFormats } = args;
29041
+ return {
29042
+ callbacks: {
29043
+ label: function (tooltipItem) {
29044
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29045
+ const yLabel = tooltipItem.parsed.r;
29046
+ const formattedY = formatValue(yLabel, { format: axisFormats?.r, locale });
29047
+ return xLabel ? `${xLabel}: ${formattedY}` : formattedY;
29048
+ },
29049
+ },
29050
+ };
29051
+ }
29052
+ function calculatePercentage(dataset, dataIndex) {
29053
+ const numericData = dataset.filter((value) => typeof value === "number");
29054
+ const total = numericData.reduce((sum, value) => sum + value, 0);
29055
+ if (!total) {
29056
+ return "";
28022
29057
  }
28023
- return undefined;
29058
+ const percentage = (dataset[dataIndex] / total) * 100;
29059
+ return percentage.toFixed(2);
28024
29060
  }
28025
- /**
28026
- * Custom chart.js plugin to set the background color of the canvas
28027
- * https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
28028
- */
28029
- const backgroundColorChartJSPlugin = {
28030
- id: "customCanvasBackgroundColor",
28031
- beforeDraw: (chart) => {
28032
- const { ctx } = chart;
28033
- ctx.save();
28034
- ctx.globalCompositeOperation = "destination-over";
28035
- ctx.fillStyle = "#ffffff";
28036
- ctx.fillRect(0, 0, chart.width, chart.height);
28037
- ctx.restore();
28038
- },
28039
- };
29061
+
29062
+ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29063
+ __proto__: null,
29064
+ INTERACTIVE_LEGEND_CONFIG: INTERACTIVE_LEGEND_CONFIG,
29065
+ canChartParseLabels: canChartParseLabels,
29066
+ getBarChartData: getBarChartData,
29067
+ getBarChartDatasets: getBarChartDatasets,
29068
+ getBarChartLayout: getBarChartLayout,
29069
+ getBarChartLegend: getBarChartLegend,
29070
+ getBarChartScales: getBarChartScales,
29071
+ getBarChartTooltip: getBarChartTooltip,
29072
+ getChartLabelFormat: getChartLabelFormat,
29073
+ getChartShowValues: getChartShowValues,
29074
+ getChartTitle: getChartTitle,
29075
+ getComboChartDatasets: getComboChartDatasets,
29076
+ getComboChartLegend: getComboChartLegend,
29077
+ getCommonChartLayout: getCommonChartLayout,
29078
+ getData: getData,
29079
+ getLineChartData: getLineChartData,
29080
+ getLineChartDatasets: getLineChartDatasets,
29081
+ getLineChartLayout: getLineChartLayout,
29082
+ getLineChartLegend: getLineChartLegend,
29083
+ getLineChartScales: getLineChartScales,
29084
+ getLineChartTooltip: getLineChartTooltip,
29085
+ getPieChartData: getPieChartData,
29086
+ getPieChartDatasets: getPieChartDatasets,
29087
+ getPieChartLayout: getPieChartLayout,
29088
+ getPieChartLegend: getPieChartLegend,
29089
+ getPieChartTooltip: getPieChartTooltip,
29090
+ getPyramidChartData: getPyramidChartData,
29091
+ getPyramidChartScales: getPyramidChartScales,
29092
+ getPyramidChartTooltip: getPyramidChartTooltip,
29093
+ getRadarChartData: getRadarChartData,
29094
+ getRadarChartDatasets: getRadarChartDatasets,
29095
+ getRadarChartLegend: getRadarChartLegend,
29096
+ getRadarChartScales: getRadarChartScales,
29097
+ getRadarChartTooltip: getRadarChartTooltip,
29098
+ getScatterChartDatasets: getScatterChartDatasets,
29099
+ getScatterChartLegend: getScatterChartLegend,
29100
+ getScatterChartScales: getScatterChartScales,
29101
+ getTrendDatasetForBarChart: getTrendDatasetForBarChart,
29102
+ getTrendDatasetForLineChart: getTrendDatasetForLineChart,
29103
+ getWaterfallChartLayout: getWaterfallChartLayout,
29104
+ getWaterfallChartLegend: getWaterfallChartLegend,
29105
+ getWaterfallChartScales: getWaterfallChartScales,
29106
+ getWaterfallChartTooltip: getWaterfallChartTooltip,
29107
+ getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels
29108
+ });
28040
29109
 
28041
29110
  class BarChart extends AbstractChart {
28042
29111
  dataSets;
@@ -28168,121 +29237,27 @@ class BarChart extends AbstractChart {
28168
29237
  }
28169
29238
  }
28170
29239
  function createBarChartRuntime(chart, getters) {
28171
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
28172
- let labels = labelValues.formattedValues;
28173
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
28174
- if (chart.dataSetsHaveTitle &&
28175
- dataSetsValues[0] &&
28176
- labels.length > dataSetsValues[0].data.length) {
28177
- labels.shift();
28178
- }
28179
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28180
- if (chart.aggregated) {
28181
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28182
- }
28183
- const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28184
- const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28185
- const locale = getters.getLocale();
28186
- const fontColor = chartFontColor(chart.background);
28187
- const axisFormats = chart.horizontal
28188
- ? { x: leftAxisFormat || rightAxisFormat }
28189
- : { y: leftAxisFormat, y1: rightAxisFormat };
28190
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
28191
- locale,
28192
- axisFormats,
28193
- horizontalChart: chart.horizontal,
28194
- });
28195
- const legend = {
28196
- ...INTERACTIVE_LEGEND_CONFIG,
28197
- ...getCustomLegendLabels(fontColor, {
28198
- pointStyle: "rect",
28199
- lineWidth: 3,
28200
- }),
28201
- };
28202
- if (chart.legendPosition === "none") {
28203
- legend.display = false;
28204
- }
28205
- else {
28206
- legend.position = chart.legendPosition;
28207
- }
28208
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
28209
- config.options.layout = {
28210
- padding: computeChartPadding({
28211
- displayTitle: !!chart.title.text,
28212
- displayLegend: chart.legendPosition === "top",
28213
- }),
28214
- };
28215
- config.options.indexAxis = chart.horizontal ? "y" : "x";
28216
- config.options.scales = {};
28217
29240
  const definition = chart.getDefinition();
28218
- const options = { stacked: chart.stacked, locale };
28219
- if (chart.horizontal) {
28220
- const format = leftAxisFormat || rightAxisFormat;
28221
- config.options.scales.x = getChartAxis(definition, "bottom", "values", { ...options, format });
28222
- config.options.scales.y = getChartAxis(definition, "left", "labels", options);
28223
- }
28224
- else {
28225
- config.options.scales.x = getChartAxis(definition, "bottom", "labels", options);
28226
- const leftAxisOptions = { ...options, format: leftAxisFormat };
28227
- config.options.scales.y = getChartAxis(definition, "left", "values", leftAxisOptions);
28228
- const rightAxisOptions = { ...options, format: rightAxisFormat };
28229
- config.options.scales.y1 = getChartAxis(definition, "right", "values", rightAxisOptions);
28230
- }
28231
- config.options.scales = removeFalsyAttributes(config.options.scales);
28232
- config.options.plugins.chartShowValuesPlugin = {
28233
- showValues: chart.showValues,
28234
- background: chart.background,
28235
- horizontal: chart.horizontal,
28236
- callback: formatChartDatasetValue(axisFormats, locale),
29241
+ const chartData = getBarChartData(definition, chart.dataSets, chart.labelRange, getters);
29242
+ const config = {
29243
+ type: "bar",
29244
+ data: {
29245
+ labels: chartData.labels.map(truncateLabel),
29246
+ datasets: getBarChartDatasets(definition, chartData),
29247
+ },
29248
+ options: {
29249
+ ...CHART_COMMON_OPTIONS,
29250
+ indexAxis: chart.horizontal ? "y" : "x",
29251
+ layout: getBarChartLayout(definition),
29252
+ scales: getBarChartScales(definition, chartData),
29253
+ plugins: {
29254
+ title: getChartTitle(definition),
29255
+ legend: getBarChartLegend(definition),
29256
+ tooltip: getBarChartTooltip(definition, chartData),
29257
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
29258
+ },
29259
+ },
28237
29260
  };
28238
- const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28239
- const trendDatasets = [];
28240
- for (const index in dataSetsValues) {
28241
- let { label, data } = dataSetsValues[index];
28242
- if (definition.dataSets?.[index]?.label) {
28243
- label = definition.dataSets[index].label;
28244
- }
28245
- const backgroundColor = colors.next();
28246
- const dataset = {
28247
- label,
28248
- data,
28249
- borderColor: BORDER_CHART_COLOR,
28250
- borderWidth: 1,
28251
- backgroundColor,
28252
- };
28253
- config.data.datasets.push(dataset);
28254
- dataset.yAxisID = chart.horizontal ? "y" : definition.dataSets[index].yAxisId || "y";
28255
- dataset.xAxisID = "x";
28256
- const trend = definition.dataSets?.[index].trend;
28257
- if (!trend?.display || chart.horizontal) {
28258
- continue;
28259
- }
28260
- const trendDataset = getTrendDatasetForBarChart(trend, dataset);
28261
- if (trendDataset) {
28262
- trendDatasets.push(trendDataset);
28263
- }
28264
- }
28265
- if (trendDatasets.length) {
28266
- /* We add a second x axis here to draw the trend lines, with the labels length being
28267
- * set so that the second axis points match the classical x axis
28268
- */
28269
- const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset.data.length));
28270
- config.options.scales[TREND_LINE_XAXIS_ID] = {
28271
- ...config.options.scales.x,
28272
- labels: Array(maxLength).fill(""),
28273
- offset: false,
28274
- display: false,
28275
- };
28276
- /* These datasets must be inserted after the original
28277
- * datasets to ensure the way we distinguish the originals and trendLine datasets after
28278
- */
28279
- trendDatasets.forEach((x) => config.data.datasets.push(x));
28280
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28281
- return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28282
- ? undefined
28283
- : "";
28284
- };
28285
- }
28286
29261
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
28287
29262
  }
28288
29263
 
@@ -28415,102 +29390,26 @@ class ComboChart extends AbstractChart {
28415
29390
  }
28416
29391
  }
28417
29392
  function createComboChartRuntime(chart, getters) {
28418
- const mainDataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28419
- const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28420
- const locale = getters.getLocale();
28421
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
28422
- let labels = labelValues.formattedValues;
28423
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
28424
- if (chart.dataSetsHaveTitle &&
28425
- dataSetsValues[0] &&
28426
- labels.length > dataSetsValues[0].data.length) {
28427
- labels.shift();
28428
- }
28429
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28430
- if (chart.aggregated) {
28431
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28432
- }
28433
- const fontColor = chartFontColor(chart.background);
28434
- const axisFormats = { y: mainDataSetFormat, y1: lineDataSetsFormat };
28435
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, { locale, axisFormats });
28436
- const legend = {
28437
- ...INTERACTIVE_LEGEND_CONFIG,
28438
- ...getCustomLegendLabels(fontColor, {
28439
- lineWidth: 3,
28440
- }),
28441
- };
28442
- if (chart.legendPosition === "none") {
28443
- legend.display = false;
28444
- }
28445
- else {
28446
- legend.position = chart.legendPosition;
28447
- }
28448
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
28449
- config.options.layout = {
28450
- padding: computeChartPadding({
28451
- displayTitle: !!chart.title.text,
28452
- displayLegend: chart.legendPosition === "top",
28453
- }),
28454
- };
28455
29393
  const definition = chart.getDefinition();
28456
- config.options.scales = {
28457
- x: getChartAxis(definition, "bottom", "labels", { locale }),
28458
- y: getChartAxis(definition, "left", "values", { locale, format: mainDataSetFormat }),
28459
- y1: getChartAxis(definition, "right", "values", { locale, format: lineDataSetsFormat }),
28460
- };
28461
- config.options.scales = removeFalsyAttributes(config.options.scales);
28462
- config.options.plugins.chartShowValuesPlugin = {
28463
- showValues: chart.showValues,
28464
- background: chart.background,
28465
- callback: formatChartDatasetValue(axisFormats, locale),
29394
+ const chartData = getBarChartData(definition, chart.dataSets, chart.labelRange, getters);
29395
+ const config = {
29396
+ type: "bar",
29397
+ data: {
29398
+ labels: chartData.labels.map(truncateLabel),
29399
+ datasets: getComboChartDatasets(definition, chartData),
29400
+ },
29401
+ options: {
29402
+ ...CHART_COMMON_OPTIONS,
29403
+ layout: getBarChartLayout(definition),
29404
+ scales: getBarChartScales(definition, chartData),
29405
+ plugins: {
29406
+ title: getChartTitle(definition),
29407
+ legend: getComboChartLegend(definition),
29408
+ tooltip: getBarChartTooltip(definition, chartData),
29409
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
29410
+ },
29411
+ },
28466
29412
  };
28467
- const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28468
- let maxLength = 0;
28469
- const trendDatasets = [];
28470
- for (let [index, { label, data }] of dataSetsValues.entries()) {
28471
- const design = definition.dataSets[index];
28472
- const color = colors.next();
28473
- const type = design?.type ?? "line";
28474
- const dataset = {
28475
- label: design?.label ?? label,
28476
- data,
28477
- borderColor: color,
28478
- backgroundColor: color,
28479
- yAxisID: design?.yAxisId ?? "y",
28480
- type,
28481
- order: type === "bar" ? dataSetsValues.length + index : index,
28482
- };
28483
- config.data.datasets.push(dataset);
28484
- const trend = definition.dataSets?.[index].trend;
28485
- if (!trend?.display) {
28486
- continue;
28487
- }
28488
- maxLength = Math.max(maxLength, data.length);
28489
- const trendDataset = getTrendDatasetForBarChart(trend, dataset);
28490
- if (trendDataset) {
28491
- trendDatasets.push(trendDataset);
28492
- }
28493
- }
28494
- if (trendDatasets.length) {
28495
- /* We add a second x axis here to draw the trend lines, with the labels length being
28496
- * set so that the second axis points match the classical x axis
28497
- */
28498
- const trendLinesMaxLength = Math.max(...trendDatasets.map((trend) => trend.data.length));
28499
- config.options.scales[TREND_LINE_XAXIS_ID] = {
28500
- labels: Array(Math.round(trendLinesMaxLength)).fill(""),
28501
- offset: false,
28502
- display: false,
28503
- };
28504
- /* These datasets must be inserted after the original datasets to ensure the way we
28505
- * distinguish the originals and trendLine datasets after
28506
- */
28507
- trendDatasets.forEach((x) => config.data.datasets.push(x));
28508
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28509
- return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28510
- ? undefined
28511
- : "";
28512
- };
28513
- }
28514
29413
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
28515
29414
  }
28516
29415
 
@@ -28753,417 +29652,6 @@ function getSectionThresholdValue(threshold, minValue, maxValue) {
28753
29652
  return clip(value, minValue, maxValue);
28754
29653
  }
28755
29654
 
28756
- const UNIT_LENGTH = {
28757
- second: 1000,
28758
- minute: 1000 * 60,
28759
- hour: 1000 * 3600,
28760
- day: 1000 * 3600 * 24,
28761
- month: 1000 * 3600 * 24 * 30,
28762
- year: 1000 * 3600 * 24 * 365,
28763
- };
28764
- const Milliseconds = {
28765
- inSeconds: function (milliseconds) {
28766
- return Math.floor(milliseconds / UNIT_LENGTH.second);
28767
- },
28768
- inMinutes: function (milliseconds) {
28769
- return Math.floor(milliseconds / UNIT_LENGTH.minute);
28770
- },
28771
- inHours: function (milliseconds) {
28772
- return Math.floor(milliseconds / UNIT_LENGTH.hour);
28773
- },
28774
- inDays: function (milliseconds) {
28775
- return Math.floor(milliseconds / UNIT_LENGTH.day);
28776
- },
28777
- inMonths: function (milliseconds) {
28778
- return Math.floor(milliseconds / UNIT_LENGTH.month);
28779
- },
28780
- inYears: function (milliseconds) {
28781
- return Math.floor(milliseconds / UNIT_LENGTH.year);
28782
- },
28783
- };
28784
- /**
28785
- * Regex to test if a format string is a date format that can be translated into a luxon time format
28786
- */
28787
- const timeFormatLuxonCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
28788
- /** Get the time options for the XAxis of ChartJS */
28789
- function getChartTimeOptions(labels, labelFormat, locale) {
28790
- const luxonFormat = convertDateFormatForLuxon(labelFormat);
28791
- const timeUnit = getBestTimeUnitForScale(labels, luxonFormat, locale);
28792
- const displayFormats = {};
28793
- if (timeUnit) {
28794
- displayFormats[timeUnit] = luxonFormat;
28795
- }
28796
- return {
28797
- parser: luxonFormat,
28798
- displayFormats,
28799
- unit: timeUnit ?? false,
28800
- };
28801
- }
28802
- /**
28803
- * Convert the given date format into a format that moment.js understands.
28804
- *
28805
- * https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens
28806
- */
28807
- function convertDateFormatForLuxon(format) {
28808
- // "m" before "h" === month, "m" after "h" === minute
28809
- const indexH = format.indexOf("h");
28810
- if (indexH >= 0) {
28811
- format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
28812
- }
28813
- else {
28814
- format = format.replace(/m/g, "M");
28815
- }
28816
- // If we have an "a", we should display hours as AM/PM (h), otherwise display 24 hours format (H)
28817
- if (!format.includes("a")) {
28818
- format = format.replace(/h/g, "H");
28819
- }
28820
- return format;
28821
- }
28822
- /** Get the minimum time unit that the format is able to display */
28823
- function getFormatMinDisplayUnit(format) {
28824
- if (format.includes("s")) {
28825
- return "second";
28826
- }
28827
- else if (format.includes("m")) {
28828
- return "minute";
28829
- }
28830
- else if (format.includes("h") || format.includes("H")) {
28831
- return "hour";
28832
- }
28833
- else if (format.includes("d")) {
28834
- return "day";
28835
- }
28836
- else if (format.includes("M")) {
28837
- return "month";
28838
- }
28839
- return "year";
28840
- }
28841
- /**
28842
- * Returns the best time unit that should be used for the X axis of a chart in order to display all
28843
- * the labels correctly.
28844
- *
28845
- * There is two conditions :
28846
- * - the format of the labels should be able to display the unit. For example if the format is "DD/MM/YYYY"
28847
- * it makes no sense to try to use minutes in the X axis
28848
- * - we want the "best fit" unit. For example if the labels span a period of several days, we want to use days
28849
- * as a unit, but if they span 200 days, we'd like to use months instead
28850
- *
28851
- */
28852
- function getBestTimeUnitForScale(labels, format, locale) {
28853
- const labelDates = labels.map((label) => parseDateTime(label, locale)?.jsDate);
28854
- if (labelDates.some((date) => date === undefined) || labels.length < 2) {
28855
- return undefined;
28856
- }
28857
- const labelsTimestamps = labelDates.map((date) => date.getTime());
28858
- const period = largeMax(labelsTimestamps) - largeMin(labelsTimestamps);
28859
- const minUnit = getFormatMinDisplayUnit(format);
28860
- if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {
28861
- return "second";
28862
- }
28863
- else if (UNIT_LENGTH.minute >= UNIT_LENGTH[minUnit] && Milliseconds.inMinutes(period) < 180) {
28864
- return "minute";
28865
- }
28866
- else if (UNIT_LENGTH.hour >= UNIT_LENGTH[minUnit] && Milliseconds.inHours(period) < 96) {
28867
- return "hour";
28868
- }
28869
- else if (UNIT_LENGTH.day >= UNIT_LENGTH[minUnit] && Milliseconds.inDays(period) < 90) {
28870
- return "day";
28871
- }
28872
- else if (UNIT_LENGTH.month >= UNIT_LENGTH[minUnit] && Milliseconds.inMonths(period) < 36) {
28873
- return "month";
28874
- }
28875
- return "year";
28876
- }
28877
-
28878
- function fixEmptyLabelsForDateCharts(labels, dataSetsValues) {
28879
- if (labels.length === 0 || labels.every((label) => !label)) {
28880
- return { labels, dataSetsValues };
28881
- }
28882
- const newLabels = [...labels];
28883
- const newDatasets = deepCopy(dataSetsValues);
28884
- for (let i = 0; i < newLabels.length; i++) {
28885
- if (!newLabels[i]) {
28886
- newLabels[i] = findNextDefinedValue(newLabels, i);
28887
- for (let ds of newDatasets) {
28888
- ds.data[i] = undefined;
28889
- }
28890
- }
28891
- }
28892
- return { labels: newLabels, dataSetsValues: newDatasets };
28893
- }
28894
- function canChartParseLabels(labelRange, getters) {
28895
- return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);
28896
- }
28897
- function getChartAxisType(chart, getters) {
28898
- if (isDateChart(chart, getters) && isLuxonTimeAdapterInstalled()) {
28899
- return "time";
28900
- }
28901
- if (isLinearChart(chart, getters)) {
28902
- return "linear";
28903
- }
28904
- return "category";
28905
- }
28906
- function isDateChart(chart, getters) {
28907
- return !chart.labelsAsText && canBeDateChart(chart.labelRange, getters);
28908
- }
28909
- function isLinearChart(chart, getters) {
28910
- return !chart.labelsAsText && canBeLinearChart(chart.labelRange, getters);
28911
- }
28912
- function canBeDateChart(labelRange, getters) {
28913
- if (!labelRange || !canBeLinearChart(labelRange, getters)) {
28914
- return false;
28915
- }
28916
- const labelFormat = getters.getEvaluatedCell({
28917
- sheetId: labelRange.sheetId,
28918
- col: labelRange.zone.left,
28919
- row: labelRange.zone.top,
28920
- }).format;
28921
- return Boolean(labelFormat && timeFormatLuxonCompatible.test(labelFormat));
28922
- }
28923
- function canBeLinearChart(labelRange, getters) {
28924
- if (!labelRange) {
28925
- return false;
28926
- }
28927
- const labels = getters.getRangeValues(labelRange);
28928
- if (labels.some((label) => isNaN(Number(label)) && label)) {
28929
- return false;
28930
- }
28931
- if (labels.every((label) => !label)) {
28932
- return false;
28933
- }
28934
- return true;
28935
- }
28936
- let missingTimeAdapterAlreadyWarned = false;
28937
- function isLuxonTimeAdapterInstalled() {
28938
- if (!window.Chart) {
28939
- return false;
28940
- }
28941
- // @ts-ignore
28942
- const adapter = new window.Chart._adapters._date({});
28943
- const isInstalled = adapter._id === "luxon";
28944
- if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28945
- missingTimeAdapterAlreadyWarned = true;
28946
- console.warn("'chartjs-adapter-luxon' time adapter is not installed. Time scale axes are disabled.");
28947
- }
28948
- return isInstalled;
28949
- }
28950
- function getTrendDatasetForLineChart(config, dataset, axisType, locale) {
28951
- const filteredValues = [];
28952
- const filteredLabels = [];
28953
- const labels = [];
28954
- const datasetLength = dataset.data.length;
28955
- if (datasetLength < 2) {
28956
- return;
28957
- }
28958
- switch (axisType) {
28959
- case "category":
28960
- for (let i = 0; i < datasetLength; i++) {
28961
- if (typeof dataset.data[i] === "number") {
28962
- filteredValues.push(dataset.data[i]);
28963
- filteredLabels.push(i + 1);
28964
- }
28965
- labels.push(i + 1);
28966
- }
28967
- break;
28968
- case "linear":
28969
- for (const point of dataset.data) {
28970
- const label = Number(point.x);
28971
- if (isNaN(label)) {
28972
- continue;
28973
- }
28974
- if (typeof point.y === "number") {
28975
- filteredValues.push(point.y);
28976
- filteredLabels.push(label);
28977
- }
28978
- labels.push(label);
28979
- }
28980
- break;
28981
- case "time":
28982
- for (const point of dataset.data) {
28983
- const date = toNumber({ value: point.x }, locale);
28984
- if (point.y !== null) {
28985
- filteredValues.push(point.y);
28986
- filteredLabels.push(date);
28987
- }
28988
- labels.push(date);
28989
- }
28990
- break;
28991
- }
28992
- const xmin = Math.min(...labels);
28993
- const xmax = Math.max(...labels);
28994
- if (xmax === xmin) {
28995
- return;
28996
- }
28997
- const numberOfStep = 5 * labels.length;
28998
- const step = (xmax - xmin) / numberOfStep;
28999
- const newLabels = range(xmin, xmax + step / 2, step);
29000
- const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
29001
- if (!newValues.length) {
29002
- return;
29003
- }
29004
- return getFullTrendingLineDataSet(dataset, config, newValues);
29005
- }
29006
- function createLineOrScatterChartRuntime(chart, getters) {
29007
- const axisType = getChartAxisType(chart, getters);
29008
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
29009
- let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
29010
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
29011
- if (chart.dataSetsHaveTitle &&
29012
- dataSetsValues[0] &&
29013
- labels.length > dataSetsValues[0].data.length) {
29014
- labels.shift();
29015
- }
29016
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
29017
- if (axisType === "time") {
29018
- ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
29019
- }
29020
- if (chart.aggregated) {
29021
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
29022
- }
29023
- const locale = getters.getLocale();
29024
- const truncateLabels = axisType === "category";
29025
- const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29026
- const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
29027
- const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
29028
- const options = { locale, truncateLabels, axisFormats };
29029
- const fontColor = chartFontColor(chart.background);
29030
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
29031
- const legend = {
29032
- ...INTERACTIVE_LEGEND_CONFIG,
29033
- labels: {
29034
- color: fontColor,
29035
- },
29036
- };
29037
- if (chart.legendPosition === "none") {
29038
- legend.display = false;
29039
- }
29040
- else {
29041
- legend.position = chart.legendPosition;
29042
- }
29043
- Object.assign(config.options.plugins.legend || {}, legend);
29044
- config.options.layout = {
29045
- padding: computeChartPadding({
29046
- displayTitle: !!chart.title.text,
29047
- displayLegend: chart.legendPosition === "top",
29048
- }),
29049
- };
29050
- const definition = chart.getDefinition();
29051
- const stacked = "stacked" in chart && chart.stacked;
29052
- config.options.scales = {
29053
- x: getChartAxis(definition, "bottom", "labels", { locale }),
29054
- y: getChartAxis(definition, "left", "values", { locale, stacked, format: leftAxisFormat }),
29055
- y1: getChartAxis(definition, "right", "values", { locale, stacked, format: rightAxisFormat }),
29056
- };
29057
- config.options.scales = removeFalsyAttributes(config.options.scales);
29058
- config.options.plugins.chartShowValuesPlugin = {
29059
- showValues: chart.showValues,
29060
- background: chart.background,
29061
- callback: formatChartDatasetValue(axisFormats, locale),
29062
- };
29063
- if (chart.dataSetsHaveTitle &&
29064
- dataSetsValues[0] &&
29065
- labels.length > dataSetsValues[0].data.length) {
29066
- labels.shift();
29067
- }
29068
- const labelFormat = getChartLabelFormat(getters, chart.labelRange);
29069
- if (axisType === "time") {
29070
- const axis = {
29071
- type: "time",
29072
- time: getChartTimeOptions(labels, labelFormat, locale),
29073
- };
29074
- Object.assign(config.options.scales.x, axis);
29075
- config.options.scales.x.ticks.maxTicksLimit = 15;
29076
- }
29077
- else if (axisType === "linear") {
29078
- config.options.scales.x.type = "linear";
29079
- config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
29080
- config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
29081
- const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
29082
- let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
29083
- if (isNumber(label, locale)) {
29084
- label = toNumber(label, locale);
29085
- }
29086
- const formattedX = formatValue(label, { locale, format: labelFormat });
29087
- const formattedY = formatValue(dataSetPoint, { locale, format: leftAxisFormat });
29088
- const dataSetTitle = tooltipItem.dataset.label;
29089
- return formattedX
29090
- ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
29091
- : `${dataSetTitle}: ${formattedY}`;
29092
- };
29093
- }
29094
- const areaChart = "fillArea" in chart ? chart.fillArea : false;
29095
- const stackedChart = "stacked" in chart ? chart.stacked : false;
29096
- const cumulative = "cumulative" in chart ? chart.cumulative : false;
29097
- const colors = getChartColorsGenerator(definition, dataSetsValues.length);
29098
- let maxLength = 0;
29099
- const trendDatasets = [];
29100
- for (let [index, { label, data }] of dataSetsValues.entries()) {
29101
- if (cumulative) {
29102
- let accumulator = 0;
29103
- data = data.map((value) => {
29104
- if (!isNaN(value)) {
29105
- accumulator += parseFloat(value);
29106
- return accumulator;
29107
- }
29108
- return value;
29109
- });
29110
- }
29111
- if (["linear", "time"].includes(axisType)) {
29112
- // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
29113
- data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
29114
- }
29115
- const color = colors.next();
29116
- if (definition.dataSets?.[index]?.label) {
29117
- label = definition.dataSets[index].label;
29118
- }
29119
- const dataset = {
29120
- label,
29121
- data,
29122
- tension: 0, // 0 -> render straight lines, which is much faster
29123
- borderColor: color,
29124
- backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
29125
- pointBackgroundColor: color,
29126
- fill: areaChart ? getFillingMode(index, stackedChart) : false,
29127
- yAxisID: definition.dataSets[index].yAxisId || "y",
29128
- };
29129
- config.data.datasets.push(dataset);
29130
- const trend = definition.dataSets?.[index].trend;
29131
- if (!trend?.display) {
29132
- continue;
29133
- }
29134
- const trendDataset = getTrendDatasetForLineChart(trend, dataset, axisType, locale);
29135
- if (trendDataset) {
29136
- maxLength = Math.max(maxLength, trendDataset.data.length);
29137
- trendDatasets.push(trendDataset);
29138
- }
29139
- }
29140
- if (trendDatasets.length) {
29141
- /* We add a second x axis here to draw the trend lines, with the labels length being
29142
- * set so that the second axis points match the classical x axis
29143
- */
29144
- config.options.scales[TREND_LINE_XAXIS_ID] = {
29145
- ...config.options.scales.x,
29146
- type: "category",
29147
- labels: range(0, maxLength).map((x) => x.toString()),
29148
- offset: false,
29149
- display: false,
29150
- };
29151
- /* These datasets must be inserted after the original datasets to ensure the way we
29152
- * distinguish the originals and trendLine datasets after
29153
- */
29154
- trendDatasets.forEach((x) => config.data.datasets.push(x));
29155
- }
29156
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29157
- const displayTooltipTitle = axisType !== "linear" &&
29158
- tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
29159
- return displayTooltipTitle ? undefined : "";
29160
- };
29161
- return {
29162
- chartJsConfig: config,
29163
- background: chart.background || BACKGROUND_CHART_COLOR,
29164
- };
29165
- }
29166
-
29167
29655
  class LineChart extends AbstractChart {
29168
29656
  dataSets;
29169
29657
  labelRange;
@@ -29303,18 +29791,30 @@ class LineChart extends AbstractChart {
29303
29791
  }
29304
29792
  }
29305
29793
  function createLineChartRuntime(chart, getters) {
29306
- const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
29307
- const filled = chart.fillArea;
29308
- const pointStyle = filled ? "rect" : "line";
29309
- const lineWidth = filled ? 2 : 3;
29310
- chartJsConfig.options.plugins.legend.labels = {
29311
- ...chartJsConfig.options?.plugins?.legend?.labels,
29312
- ...getCustomLegendLabels(chartFontColor(background), {
29313
- pointStyle,
29314
- lineWidth,
29315
- }).labels,
29794
+ const definition = chart.getDefinition();
29795
+ const chartData = getLineChartData(definition, chart.dataSets, chart.labelRange, getters);
29796
+ const config = {
29797
+ type: "line",
29798
+ data: {
29799
+ labels: chartData.axisType !== "time" ? chartData.labels.map(truncateLabel) : chartData.labels,
29800
+ datasets: getLineChartDatasets(definition, chartData),
29801
+ },
29802
+ options: {
29803
+ ...CHART_COMMON_OPTIONS,
29804
+ layout: getLineChartLayout(definition),
29805
+ scales: getLineChartScales(definition, chartData),
29806
+ plugins: {
29807
+ title: getChartTitle(definition),
29808
+ legend: getLineChartLegend(definition),
29809
+ tooltip: getLineChartTooltip(definition, chartData),
29810
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
29811
+ },
29812
+ },
29813
+ };
29814
+ return {
29815
+ chartJsConfig: config,
29816
+ background: chart.background || BACKGROUND_CHART_COLOR,
29316
29817
  };
29317
- return { chartJsConfig, background };
29318
29818
  }
29319
29819
 
29320
29820
  class PieChart extends AbstractChart {
@@ -29425,126 +29925,26 @@ class PieChart extends AbstractChart {
29425
29925
  return new PieChart(definition, this.sheetId, this.getters);
29426
29926
  }
29427
29927
  }
29428
- function getPieConfiguration(chart, labels, localeFormat, colors) {
29429
- const fontColor = chartFontColor(chart.background);
29430
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
29431
- const legend = {
29432
- labels: {
29433
- color: fontColor,
29434
- usePointStyle: true,
29435
- //@ts-ignore
29436
- generateLabels: (c) =>
29437
- //@ts-ignore
29438
- c.data.labels.map((label, index) => ({
29439
- text: label,
29440
- strokeStyle: colors[index],
29441
- fillStyle: colors[index],
29442
- pointStyle: "rect",
29443
- hidden: false,
29444
- lineWidth: 2,
29445
- })),
29928
+ function createPieChartRuntime(chart, getters) {
29929
+ const definition = chart.getDefinition();
29930
+ const chartData = getPieChartData(definition, chart.dataSets, chart.labelRange, getters);
29931
+ const config = {
29932
+ type: chart.isDoughnut ? "doughnut" : "pie",
29933
+ data: {
29934
+ labels: chartData.labels.map(truncateLabel),
29935
+ datasets: getPieChartDatasets(definition, chartData),
29936
+ },
29937
+ options: {
29938
+ ...CHART_COMMON_OPTIONS,
29939
+ layout: getPieChartLayout(definition),
29940
+ plugins: {
29941
+ title: getChartTitle(definition),
29942
+ legend: getPieChartLegend(definition, chartData),
29943
+ tooltip: getPieChartTooltip(definition, chartData),
29944
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
29945
+ },
29446
29946
  },
29447
29947
  };
29448
- if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
29449
- legend.display = false;
29450
- }
29451
- else {
29452
- legend.position = chart.legendPosition;
29453
- }
29454
- Object.assign(config.options.plugins.legend || {}, legend);
29455
- config.options.layout = {
29456
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
29457
- };
29458
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29459
- return tooltipItems[0].dataset.label;
29460
- };
29461
- config.options.plugins.tooltip.callbacks.label = function (tooltipItem) {
29462
- const { format, locale } = localeFormat;
29463
- const data = tooltipItem.dataset.data;
29464
- const dataIndex = tooltipItem.dataIndex;
29465
- const percentage = calculatePercentage(data, dataIndex);
29466
- const xLabel = tooltipItem.label || tooltipItem.dataset.label;
29467
- const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
29468
- const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
29469
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29470
- return xLabel ? `${xLabel}: ${yLabelStr} (${percentage}%)` : `${yLabelStr} (${percentage}%)`;
29471
- };
29472
- config.options.plugins.chartShowValuesPlugin = {
29473
- showValues: chart.showValues,
29474
- callback: formatTickValue(localeFormat),
29475
- };
29476
- return config;
29477
- }
29478
- function getPieColors(colors, dataSetsValues) {
29479
- const pieColors = [];
29480
- const maxLength = largeMax(dataSetsValues.map((ds) => ds.data.length));
29481
- for (let i = 0; i <= maxLength; i++) {
29482
- pieColors.push(colors.next());
29483
- }
29484
- return pieColors;
29485
- }
29486
- function calculatePercentage(dataset, dataIndex) {
29487
- const numericData = dataset.filter((value) => typeof value === "number");
29488
- const total = numericData.reduce((sum, value) => sum + value, 0);
29489
- if (!total) {
29490
- return "";
29491
- }
29492
- const percentage = (dataset[dataIndex] / total) * 100;
29493
- return percentage.toFixed(2);
29494
- }
29495
- function filterNegativeValues(labels, datasets) {
29496
- const dataPointsIndexes = labels.reduce((indexes, label, i) => {
29497
- const shouldKeep = datasets.some((dataset) => {
29498
- const dataPoint = dataset.data[i];
29499
- return typeof dataPoint !== "number" || dataPoint >= 0;
29500
- });
29501
- if (shouldKeep) {
29502
- indexes.push(i);
29503
- }
29504
- return indexes;
29505
- }, []);
29506
- const filteredLabels = dataPointsIndexes.map((i) => labels[i] || "");
29507
- const filteredDatasets = datasets.map((dataset) => ({
29508
- ...dataset,
29509
- data: dataPointsIndexes.map((i) => {
29510
- const dataPoint = dataset.data[i];
29511
- return typeof dataPoint !== "number" || dataPoint >= 0 ? dataPoint : 0;
29512
- }),
29513
- }));
29514
- return { labels: filteredLabels, dataSetsValues: filteredDatasets };
29515
- }
29516
- function createPieChartRuntime(chart, getters) {
29517
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
29518
- let labels = labelValues.formattedValues;
29519
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
29520
- if (chart.dataSetsHaveTitle &&
29521
- dataSetsValues[0] &&
29522
- labels.length > dataSetsValues[0].data.length) {
29523
- labels.shift();
29524
- }
29525
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
29526
- if (chart.aggregated) {
29527
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
29528
- }
29529
- ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
29530
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29531
- const locale = getters.getLocale();
29532
- const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
29533
- const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
29534
- const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale }, backgroundColor);
29535
- for (const { label, data } of dataSetsValues) {
29536
- const dataset = {
29537
- label,
29538
- data,
29539
- borderColor: BACKGROUND_CHART_COLOR,
29540
- backgroundColor,
29541
- hoverOffset: 30,
29542
- };
29543
- config.data.datasets.push(dataset);
29544
- }
29545
- if (chart.isDoughnut) {
29546
- config.type = "doughnut";
29547
- }
29548
29948
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29549
29949
  }
29550
29950
 
@@ -29662,27 +30062,27 @@ class PyramidChart extends AbstractChart {
29662
30062
  }
29663
30063
  }
29664
30064
  function createPyramidChartRuntime(chart, getters) {
29665
- const barDef = { ...chart.getDefinition(), type: "bar" };
29666
- const barChart = new BarChart(barDef, chart.sheetId, getters);
29667
- const barRuntime = createBarChartRuntime(barChart, getters);
29668
- const config = barRuntime.chartJsConfig;
29669
- let datasets = config.data?.datasets;
29670
- if (datasets && datasets[0]) {
29671
- datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
29672
- }
29673
- if (datasets && datasets[1]) {
29674
- datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
29675
- }
29676
- const scales = config.options.scales;
29677
- const scalesXCallback = scales.x.ticks.callback;
29678
- scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
29679
- const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
29680
- config.options.plugins.tooltip.callbacks.label = (item) => {
29681
- const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
29682
- return tooltipLabelCallback(tooltipItem);
30065
+ const definition = chart.getDefinition();
30066
+ const chartData = getPyramidChartData(definition, chart.dataSets, chart.labelRange, getters);
30067
+ const config = {
30068
+ type: "bar",
30069
+ data: {
30070
+ labels: chartData.labels.map(truncateLabel),
30071
+ datasets: getBarChartDatasets(definition, chartData),
30072
+ },
30073
+ options: {
30074
+ ...CHART_COMMON_OPTIONS,
30075
+ indexAxis: "y",
30076
+ layout: getBarChartLayout(definition),
30077
+ scales: getPyramidChartScales(definition, chartData),
30078
+ plugins: {
30079
+ title: getChartTitle(definition),
30080
+ legend: getBarChartLegend(definition),
30081
+ tooltip: getPyramidChartTooltip(definition, chartData),
30082
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
30083
+ },
30084
+ },
29683
30085
  };
29684
- const callback = config.options.plugins.chartShowValuesPlugin.callback;
29685
- config.options.plugins.chartShowValuesPlugin.callback = (x, axisId) => callback(Math.abs(x), axisId);
29686
30086
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29687
30087
  }
29688
30088
 
@@ -29697,6 +30097,7 @@ class RadarChart extends AbstractChart {
29697
30097
  dataSetsHaveTitle;
29698
30098
  dataSetDesign;
29699
30099
  fillArea;
30100
+ showValues;
29700
30101
  constructor(definition, sheetId, getters) {
29701
30102
  super(definition, sheetId, getters);
29702
30103
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -29708,6 +30109,7 @@ class RadarChart extends AbstractChart {
29708
30109
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
29709
30110
  this.dataSetDesign = definition.dataSets;
29710
30111
  this.fillArea = definition.fillArea;
30112
+ this.showValues = definition.showValues;
29711
30113
  }
29712
30114
  static transformDefinition(definition, executed) {
29713
30115
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -29727,6 +30129,7 @@ class RadarChart extends AbstractChart {
29727
30129
  type: "radar",
29728
30130
  labelRange: context.auxiliaryRange || undefined,
29729
30131
  fillArea: context.fillArea ?? false,
30132
+ showValues: context.showValues ?? false,
29730
30133
  };
29731
30134
  }
29732
30135
  getContextCreation() {
@@ -29779,6 +30182,7 @@ class RadarChart extends AbstractChart {
29779
30182
  stacked: this.stacked,
29780
30183
  aggregated: this.aggregated,
29781
30184
  fillArea: this.fillArea,
30185
+ showValues: this.showValues,
29782
30186
  };
29783
30187
  }
29784
30188
  getDefinitionForExcel() {
@@ -29809,86 +30213,25 @@ class RadarChart extends AbstractChart {
29809
30213
  }
29810
30214
  function createRadarChartRuntime(chart, getters) {
29811
30215
  const definition = chart.getDefinition();
29812
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
29813
- let labels = labelValues.formattedValues;
29814
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
29815
- if (chart.dataSetsHaveTitle &&
29816
- dataSetsValues[0] &&
29817
- labels.length > dataSetsValues[0].data.length) {
29818
- labels.shift();
29819
- }
29820
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
29821
- if (chart.aggregated) {
29822
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
29823
- }
29824
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left") ||
29825
- getChartDatasetFormat(getters, chart.dataSets, "right");
29826
- const axisFormats = { r: dataSetFormat };
29827
- const locale = getters.getLocale();
29828
- const fontColor = chartFontColor(chart.background);
29829
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
29830
- axisFormats,
29831
- locale,
29832
- });
29833
- const fill = definition.fillArea ?? false;
29834
- const pointStyle = fill ? "rect" : "line";
29835
- const lineWidth = fill ? 2 : 3;
29836
- const legend = {
29837
- ...INTERACTIVE_LEGEND_CONFIG,
29838
- ...getCustomLegendLabels(fontColor, {
29839
- pointStyle,
29840
- lineWidth,
29841
- }),
29842
- };
29843
- if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
29844
- legend.display = false;
29845
- }
29846
- else {
29847
- legend.position = chart.legendPosition;
29848
- }
29849
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
29850
- config.options.plugins.tooltip = {
29851
- ...config.options.plugins?.tooltip,
29852
- callbacks: {
29853
- label: function (tooltipItem) {
29854
- const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
29855
- const yLabel = tooltipItem.parsed.r;
29856
- const formattedY = formatValue(yLabel, { format: dataSetFormat, locale });
29857
- return xLabel ? `${xLabel}: ${formattedY}` : formattedY;
29858
- },
30216
+ const chartData = getRadarChartData(definition, chart.dataSets, chart.labelRange, getters);
30217
+ const config = {
30218
+ type: "radar",
30219
+ data: {
30220
+ labels: chartData.labels.map(truncateLabel),
30221
+ datasets: getRadarChartDatasets(definition, chartData),
29859
30222
  },
29860
- };
29861
- config.options.scales = {
29862
- r: {
29863
- ticks: {
29864
- callback: formatTickValue({ format: dataSetFormat, locale }),
29865
- backdropColor: chart.background || "#FFFFFF",
30223
+ options: {
30224
+ ...CHART_COMMON_OPTIONS,
30225
+ layout: getBarChartLayout(definition),
30226
+ scales: getRadarChartScales(definition, chartData),
30227
+ plugins: {
30228
+ title: getChartTitle(definition),
30229
+ legend: getRadarChartLegend(definition),
30230
+ tooltip: getRadarChartTooltip(definition, chartData),
30231
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
29866
30232
  },
29867
- pointLabels: { color: fontColor },
29868
30233
  },
29869
30234
  };
29870
- config.options.layout = {
29871
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
29872
- };
29873
- const colorGenerator = getChartColorsGenerator(definition, dataSetsValues.length);
29874
- for (let i = 0; i < dataSetsValues.length; i++) {
29875
- let { label, data } = dataSetsValues[i];
29876
- if (definition.dataSets?.[i]?.label) {
29877
- label = definition.dataSets[i].label;
29878
- }
29879
- const borderColor = colorGenerator.next();
29880
- const dataset = {
29881
- label,
29882
- data,
29883
- borderColor,
29884
- backgroundColor: borderColor,
29885
- };
29886
- if (fill) {
29887
- dataset.backgroundColor = setColorAlpha(borderColor, LINE_FILL_TRANSPARENCY);
29888
- dataset["fill"] = "start";
29889
- }
29890
- config.data.datasets.push(dataset);
29891
- }
29892
30235
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29893
30236
  }
29894
30237
 
@@ -30020,20 +30363,32 @@ class ScatterChart extends AbstractChart {
30020
30363
  }
30021
30364
  }
30022
30365
  function createScatterChartRuntime(chart, getters) {
30023
- const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
30024
- // use chartJS line chart and disable the lines instead of chartJS scatter chart. This is because the scatter chart
30025
- // have less options than the line chart (it only works with linear labels)
30026
- chartJsConfig.type = "line";
30027
- const configOptions = chartJsConfig.options;
30028
- configOptions.plugins.legend.labels = {
30029
- ...configOptions.plugins?.legend?.labels,
30030
- boxHeight: 6,
30031
- usePointStyle: true,
30366
+ const definition = chart.getDefinition();
30367
+ const chartData = getLineChartData(definition, chart.dataSets, chart.labelRange, getters);
30368
+ const config = {
30369
+ // use chartJS line chart and disable the lines instead of chartJS scatter chart. This is because the scatter chart
30370
+ // have less options than the line chart (it only works with linear labels)
30371
+ type: "line",
30372
+ data: {
30373
+ labels: chartData.axisType !== "time" ? chartData.labels.map(truncateLabel) : chartData.labels,
30374
+ datasets: getScatterChartDatasets(definition, chartData),
30375
+ },
30376
+ options: {
30377
+ ...CHART_COMMON_OPTIONS,
30378
+ layout: getLineChartLayout(definition),
30379
+ scales: getScatterChartScales(definition, chartData),
30380
+ plugins: {
30381
+ title: getChartTitle(definition),
30382
+ legend: getScatterChartLegend(definition),
30383
+ tooltip: getLineChartTooltip(definition, chartData),
30384
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
30385
+ },
30386
+ },
30387
+ };
30388
+ return {
30389
+ chartJsConfig: config,
30390
+ background: chart.background || BACKGROUND_CHART_COLOR,
30032
30391
  };
30033
- for (const dataSet of chartJsConfig.data.datasets) {
30034
- dataSet.showLine = "showLine" in dataSet ? dataSet.showLine : false;
30035
- }
30036
- return { chartJsConfig, background };
30037
30392
  }
30038
30393
 
30039
30394
  class WaterfallChart extends AbstractChart {
@@ -30169,173 +30524,29 @@ class WaterfallChart extends AbstractChart {
30169
30524
  return new WaterfallChart(definition, this.sheetId, this.getters);
30170
30525
  }
30171
30526
  }
30172
- function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat) {
30173
- const { locale, format } = localeFormat;
30174
- const fontColor = chartFontColor(chart.background);
30175
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
30176
- const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
30177
- const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
30178
- const subTotalColor = chart.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
30179
- const pointStyle = "rect";
30180
- const legend = {
30181
- labels: {
30182
- usePointStyle: true,
30183
- generateLabels: () => {
30184
- const legendValues = [
30185
- {
30186
- text: _t("Positive values"),
30187
- fontColor,
30188
- strokeStyle: positiveColor,
30189
- fillStyle: positiveColor,
30190
- pointStyle,
30191
- },
30192
- {
30193
- text: _t("Negative values"),
30194
- fontColor,
30195
- strokeStyle: negativeColor,
30196
- fillStyle: negativeColor,
30197
- pointStyle,
30198
- },
30199
- ];
30200
- if (chart.showSubTotals || chart.firstValueAsSubtotal) {
30201
- legendValues.push({
30202
- text: _t("Subtotals"),
30203
- fontColor,
30204
- strokeStyle: subTotalColor,
30205
- fillStyle: subTotalColor,
30206
- pointStyle,
30207
- });
30208
- }
30209
- return legendValues;
30210
- },
30211
- },
30212
- };
30213
- if (chart.legendPosition === "none") {
30214
- legend.display = false;
30215
- }
30216
- else {
30217
- legend.position = chart.legendPosition;
30218
- }
30219
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
30220
- config.options.layout = {
30221
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
30222
- };
30223
- config.options.scales = {
30224
- x: {
30225
- ticks: {
30226
- padding: 5,
30227
- color: fontColor,
30228
- },
30229
- grid: {
30230
- display: false,
30231
- },
30232
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
30527
+ function createWaterfallChartRuntime(chart, getters) {
30528
+ const definition = chart.getDefinition();
30529
+ const chartData = getBarChartData(definition, chart.dataSets, chart.labelRange, getters);
30530
+ const { labels, datasets } = getWaterfallDatasetAndLabels(definition, chartData);
30531
+ const config = {
30532
+ type: "bar",
30533
+ data: {
30534
+ labels,
30535
+ datasets,
30233
30536
  },
30234
- y: {
30235
- position: chart.verticalAxisPosition,
30236
- ticks: {
30237
- color: fontColor,
30238
- callback: (value) => {
30239
- value = Number(value);
30240
- if (isNaN(value))
30241
- return value;
30242
- return formatValue(value, {
30243
- locale,
30244
- format: !format && Math.abs(value) > 1000 ? "#,##" : format,
30245
- });
30246
- },
30247
- },
30248
- grid: {
30249
- lineWidth: (context) => {
30250
- return context.tick.value === 0 ? 2 : 1;
30251
- },
30537
+ options: {
30538
+ ...CHART_COMMON_OPTIONS,
30539
+ layout: getWaterfallChartLayout(definition),
30540
+ scales: getWaterfallChartScales(definition, chartData),
30541
+ plugins: {
30542
+ title: getChartTitle(definition),
30543
+ legend: getWaterfallChartLegend(definition),
30544
+ tooltip: getWaterfallChartTooltip(definition, chartData),
30545
+ chartShowValuesPlugin: getChartShowValues(definition, chartData),
30546
+ waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
30252
30547
  },
30253
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
30254
30548
  },
30255
30549
  };
30256
- config.options.plugins.tooltip = {
30257
- callbacks: {
30258
- label: function (tooltipItem) {
30259
- const [lastValue, currentValue] = tooltipItem.raw;
30260
- const yLabel = currentValue - lastValue;
30261
- const dataSeriesIndex = Math.floor(tooltipItem.dataIndex / labels.length);
30262
- const dataSeriesLabel = dataSeriesLabels[dataSeriesIndex];
30263
- const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
30264
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
30265
- return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
30266
- },
30267
- },
30268
- };
30269
- config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
30270
- config.options.plugins.chartShowValuesPlugin = {
30271
- showValues: chart.showValues,
30272
- background: chart.background,
30273
- callback: formatTickValue(localeFormat),
30274
- };
30275
- return config;
30276
- }
30277
- function createWaterfallChartRuntime(chart, getters) {
30278
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
30279
- let labels = labelValues.formattedValues;
30280
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
30281
- if (chart.dataSetsHaveTitle &&
30282
- dataSetsValues[0] &&
30283
- labels.length > dataSetsValues[0].data.length) {
30284
- labels.shift();
30285
- }
30286
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
30287
- if (chart.aggregated) {
30288
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
30289
- }
30290
- if (chart.showSubTotals) {
30291
- labels.push(_t("Subtotal"));
30292
- }
30293
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left") ||
30294
- getChartDatasetFormat(getters, chart.dataSets, "right");
30295
- const locale = getters.getLocale();
30296
- const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
30297
- const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
30298
- format: dataSetFormat,
30299
- locale,
30300
- });
30301
- config.type = "bar";
30302
- const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
30303
- const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
30304
- const subTotalColor = chart.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
30305
- const backgroundColor = [];
30306
- const datasetValues = [];
30307
- const dataset = {
30308
- label: "",
30309
- data: datasetValues,
30310
- backgroundColor,
30311
- };
30312
- const labelsWithSubTotals = [];
30313
- let lastValue = 0;
30314
- for (const dataSetsValue of dataSetsValues) {
30315
- for (let i = 0; i < dataSetsValue.data.length; i++) {
30316
- const data = dataSetsValue.data[i];
30317
- labelsWithSubTotals.push(labels[i]);
30318
- if (isNaN(Number(data))) {
30319
- datasetValues.push([lastValue, lastValue]);
30320
- backgroundColor.push("");
30321
- continue;
30322
- }
30323
- datasetValues.push([lastValue, data + lastValue]);
30324
- let color = data >= 0 ? positiveColor : negativeColor;
30325
- if (i === 0 && dataSetsValue === dataSetsValues[0] && chart.firstValueAsSubtotal) {
30326
- color = subTotalColor;
30327
- }
30328
- backgroundColor.push(color);
30329
- lastValue += data;
30330
- }
30331
- if (chart.showSubTotals) {
30332
- labelsWithSubTotals.push(_t("Subtotal"));
30333
- datasetValues.push([0, lastValue]);
30334
- backgroundColor.push(subTotalColor);
30335
- }
30336
- }
30337
- config.data.datasets.push(dataset);
30338
- config.data.labels = labelsWithSubTotals.map(truncateLabel);
30339
30550
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30340
30551
  }
30341
30552
 
@@ -30766,7 +30977,7 @@ function getCopyMenuItem(figureId, env) {
30766
30977
  env.model.dispatch("COPY");
30767
30978
  await env.clipboard.write(env.model.getters.getClipboardContent());
30768
30979
  },
30769
- icon: "o-spreadsheet-Icon.COPY",
30980
+ icon: "o-spreadsheet-Icon.CLIPBOARD",
30770
30981
  };
30771
30982
  }
30772
30983
  function getCutMenuItem(figureId, env) {
@@ -32554,14 +32765,14 @@ function transformDefinition(definition, executed) {
32554
32765
  * - Else returns a bar chart
32555
32766
  */
32556
32767
  function getSmartChartDefinition(zone, getters) {
32768
+ const sheetId = getters.getActiveSheetId();
32557
32769
  let dataSetZone = zone;
32558
32770
  const singleColumn = zoneToDimension(zone).numberOfCols === 1;
32559
32771
  if (!singleColumn) {
32560
32772
  dataSetZone = { ...zone, left: zone.left + 1 };
32561
32773
  }
32562
- const dataRange = zoneToXc(dataSetZone);
32774
+ const dataRange = zoneToXc(getters.getUnboundedZone(sheetId, dataSetZone));
32563
32775
  const dataSets = [{ dataRange, yAxisId: "y" }];
32564
- const sheetId = getters.getActiveSheetId();
32565
32776
  const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
32566
32777
  if (getZoneArea(zone) === 1 && topLeftCell?.content) {
32567
32778
  return {
@@ -32581,10 +32792,7 @@ function getSmartChartDefinition(zone, getters) {
32581
32792
  const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
32582
32793
  let labelRangeXc;
32583
32794
  if (!singleColumn) {
32584
- labelRangeXc = zoneToXc({
32585
- ...zone,
32586
- right: zone.left,
32587
- });
32795
+ labelRangeXc = zoneToXc(getters.getUnboundedZone(sheetId, { ...zone, right: zone.left }));
32588
32796
  }
32589
32797
  // Only display legend for several datasets.
32590
32798
  const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
@@ -32628,6 +32836,51 @@ function getSmartChartDefinition(zone, getters) {
32628
32836
  };
32629
32837
  }
32630
32838
 
32839
+ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
32840
+ __proto__: null,
32841
+ AbstractChart: AbstractChart,
32842
+ BarChart: BarChart,
32843
+ CHART_AXIS_CHOICES: CHART_AXIS_CHOICES,
32844
+ CHART_COMMON_OPTIONS: CHART_COMMON_OPTIONS,
32845
+ GaugeChart: GaugeChart,
32846
+ LineChart: LineChart,
32847
+ PieChart: PieChart,
32848
+ ScorecardChart: ScorecardChart$1,
32849
+ TREND_LINE_XAXIS_ID: TREND_LINE_XAXIS_ID,
32850
+ WaterfallChart: WaterfallChart,
32851
+ adaptChartRange: adaptChartRange,
32852
+ chartFactory: chartFactory,
32853
+ chartFontColor: chartFontColor,
32854
+ chartRuntimeFactory: chartRuntimeFactory,
32855
+ chartToImage: chartToImage,
32856
+ checkDataset: checkDataset,
32857
+ checkLabelRange: checkLabelRange,
32858
+ copyDataSetsWithNewSheetId: copyDataSetsWithNewSheetId,
32859
+ copyLabelRangeWithNewSheetId: copyLabelRangeWithNewSheetId,
32860
+ createBarChartRuntime: createBarChartRuntime,
32861
+ createDataSets: createDataSets,
32862
+ createGaugeChartRuntime: createGaugeChartRuntime,
32863
+ createLineChartRuntime: createLineChartRuntime,
32864
+ createPieChartRuntime: createPieChartRuntime,
32865
+ createScorecardChartRuntime: createScorecardChartRuntime,
32866
+ createWaterfallChartRuntime: createWaterfallChartRuntime,
32867
+ drawScoreChart: drawScoreChart,
32868
+ formatChartDatasetValue: formatChartDatasetValue,
32869
+ formatTickValue: formatTickValue,
32870
+ getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
32871
+ getDefinedAxis: getDefinedAxis,
32872
+ getPieColors: getPieColors,
32873
+ getSmartChartDefinition: getSmartChartDefinition,
32874
+ shouldRemoveFirstLabel: shouldRemoveFirstLabel,
32875
+ toExcelDataset: toExcelDataset,
32876
+ toExcelLabelRange: toExcelLabelRange,
32877
+ transformChartDefinitionWithDataSetsWithZone: transformChartDefinitionWithDataSetsWithZone,
32878
+ transformDefinition: transformDefinition,
32879
+ truncateLabel: truncateLabel,
32880
+ updateChartRangesWithDataSets: updateChartRangesWithDataSets,
32881
+ validateChartDefinition: validateChartDefinition
32882
+ });
32883
+
32631
32884
  /**
32632
32885
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
32633
32886
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
@@ -33201,7 +33454,7 @@ const copy = {
33201
33454
  env.model.dispatch("COPY");
33202
33455
  await env.clipboard.write(env.model.getters.getClipboardContent());
33203
33456
  },
33204
- icon: "o-spreadsheet-Icon.COPY",
33457
+ icon: "o-spreadsheet-Icon.CLIPBOARD",
33205
33458
  };
33206
33459
  const cut = {
33207
33460
  name: _t("Cut"),
@@ -36685,6 +36938,7 @@ class ColorPicker extends Component {
36685
36938
  currentColor: { type: String, optional: true },
36686
36939
  maxHeight: { type: Number, optional: true },
36687
36940
  anchorRect: Object,
36941
+ disableNoColor: { type: Boolean, optional: true },
36688
36942
  };
36689
36943
  static defaultProps = { currentColor: "" };
36690
36944
  static components = { Popover };
@@ -37084,6 +37338,7 @@ class RoundColorPicker extends Component {
37084
37338
  currentColor: { type: String, optional: true },
37085
37339
  title: { type: String, optional: true },
37086
37340
  onColorPicked: Function,
37341
+ disableNoColor: { type: Boolean, optional: true },
37087
37342
  };
37088
37343
  colorPickerButtonRef = useRef("colorPickerButton");
37089
37344
  state;
@@ -40252,6 +40507,9 @@ class ConditionalFormattingEditor extends Component {
40252
40507
  }
40253
40508
  }
40254
40509
  setColorScaleColor(target, color) {
40510
+ if (!isColorValid(color)) {
40511
+ return;
40512
+ }
40255
40513
  const point = this.state.rules.colorScale[target];
40256
40514
  if (point) {
40257
40515
  point.color = Number.parseInt(color.slice(1), 16);
@@ -51162,16 +51420,11 @@ class BordersPlugin extends CorePlugin {
51162
51420
  import(data) {
51163
51421
  // Borders
51164
51422
  if (Object.keys(data.borders || {}).length) {
51165
- for (let sheet of data.sheets) {
51166
- for (const zoneXc in sheet.borders) {
51167
- const borderId = sheet.borders[zoneXc];
51423
+ for (const sheet of data.sheets) {
51424
+ for (const [position, borderId] of iterateItemIdsPositions(sheet.id, sheet.borders)) {
51425
+ const { sheetId, col, row } = position;
51168
51426
  const border = data.borders[borderId];
51169
- const zone = toZone(zoneXc);
51170
- for (let row = zone.top; row <= zone.bottom; row++) {
51171
- for (let col = zone.left; col <= zone.right; col++) {
51172
- this.setBorder(sheet.id, col, row, border, false);
51173
- }
51174
- }
51427
+ this.setBorder(sheetId, col, row, border, false);
51175
51428
  }
51176
51429
  }
51177
51430
  }
@@ -51204,23 +51457,17 @@ class BordersPlugin extends CorePlugin {
51204
51457
  data.borders = borders;
51205
51458
  }
51206
51459
  exportForExcel(data) {
51207
- for (const sheet of data.sheets) {
51208
- for (let col = 0; col < sheet.colNumber; col++) {
51209
- for (let row = 0; row < sheet.rowNumber; row++) {
51210
- const border = this.getCellBorder({ sheetId: sheet.id, col, row });
51211
- if (border) {
51212
- const xc = toXC(col, row);
51213
- sheet.cells[xc] ??= {};
51214
- sheet.cells[xc].border = getItemId(border, data.borders);
51215
- }
51216
- }
51217
- }
51218
- }
51460
+ this.export(data);
51219
51461
  }
51220
51462
  }
51221
51463
 
51222
51464
  class PositionMap {
51223
51465
  map = {};
51466
+ constructor(entries = []) {
51467
+ for (const [position, value] of entries) {
51468
+ this.set(position, value);
51469
+ }
51470
+ }
51224
51471
  set({ sheetId, col, row }, value) {
51225
51472
  const map = this.map;
51226
51473
  if (!map[sheetId]) {
@@ -51457,10 +51704,10 @@ class CellPlugin extends CorePlugin {
51457
51704
  const cellsData = new PositionMap();
51458
51705
  // cells content
51459
51706
  for (const xc in sheet.cells) {
51460
- if (sheet.cells[xc]?.content) {
51707
+ if (sheet.cells[xc]) {
51461
51708
  const { col, row } = toCartesian(xc);
51462
51709
  const position = { sheetId: sheet.id, col, row };
51463
- cellsData.set(position, { content: sheet.cells[xc]?.content });
51710
+ cellsData.set(position, { content: sheet.cells[xc] });
51464
51711
  }
51465
51712
  }
51466
51713
  // cells style and format
@@ -51468,20 +51715,13 @@ class CellPlugin extends CorePlugin {
51468
51715
  ["style", sheet.styles],
51469
51716
  ["format", sheet.formats],
51470
51717
  ]) {
51471
- for (const zoneXc in valuesByZones) {
51472
- const itemId = valuesByZones[zoneXc];
51473
- const zone = toZone(zoneXc);
51474
- for (let row = zone.top; row <= zone.bottom; row++) {
51475
- for (let col = zone.left; col <= zone.right; col++) {
51476
- const position = { sheetId, col, row };
51477
- const cellData = cellsData.get(position);
51478
- if (cellData) {
51479
- cellData[cellProperty] = itemId;
51480
- }
51481
- else {
51482
- cellsData.set(position, { [cellProperty]: itemId });
51483
- }
51484
- }
51718
+ for (const [position, itemId] of iterateItemIdsPositions(sheet.id, valuesByZones)) {
51719
+ const cellData = cellsData.get(position);
51720
+ if (cellData) {
51721
+ cellData[cellProperty] = itemId;
51722
+ }
51723
+ else {
51724
+ cellsData.set(position, { [cellProperty]: itemId });
51485
51725
  }
51486
51726
  }
51487
51727
  }
@@ -51523,9 +51763,7 @@ class CellPlugin extends CorePlugin {
51523
51763
  positionsByFormat[formatId].push(position);
51524
51764
  }
51525
51765
  if (cell.content) {
51526
- cells[xc] = {
51527
- content: cell.content,
51528
- };
51766
+ cells[xc] = cell.content;
51529
51767
  }
51530
51768
  }
51531
51769
  _sheet.styles = groupItemIdsByZones(positionsByStyle);
@@ -51541,35 +51779,19 @@ class CellPlugin extends CorePlugin {
51541
51779
  }
51542
51780
  exportForExcel(data) {
51543
51781
  this.export(data);
51544
- for (const sheet of data.sheets) {
51545
- for (const cellId in this.getters.getCells(sheet.id)) {
51546
- const { col, row } = this.getters.getCellPosition(cellId);
51547
- const xc = toXC(col, row);
51548
- const cell = this.getCellById(cellId);
51549
- sheet.cells[xc] ??= {};
51550
- if (cell?.format) {
51551
- sheet.cells[xc].format = getItemId(cell.format, data.formats);
51552
- }
51553
- if (cell?.style) {
51554
- sheet.cells[xc] ??= {};
51555
- sheet.cells[xc].style = getItemId(this.removeDefaultStyleValues(cell.style), data.styles);
51556
- }
51557
- }
51558
- }
51559
- const incompatible = [];
51782
+ const incompatibleIds = [];
51560
51783
  for (const formatId in data.formats || []) {
51561
51784
  if (!isExcelCompatible(data.formats[formatId])) {
51562
- incompatible.push(formatId);
51785
+ incompatibleIds.push(Number(formatId));
51563
51786
  delete data.formats[formatId];
51564
51787
  }
51565
51788
  }
51566
- if (incompatible.length) {
51789
+ if (incompatibleIds.length) {
51567
51790
  for (const sheet of data.sheets) {
51568
- for (const xc in sheet.cells) {
51569
- const cell = sheet.cells[xc];
51570
- const format = cell?.format;
51571
- if (format && incompatible.includes(format.toString())) {
51572
- delete cell.format;
51791
+ for (const zoneXc in sheet.formats) {
51792
+ const formatId = sheet.formats[zoneXc];
51793
+ if (formatId && incompatibleIds.includes(formatId)) {
51794
+ delete sheet.formats[zoneXc];
51573
51795
  }
51574
51796
  }
51575
51797
  }
@@ -58368,42 +58590,44 @@ class EvaluationPlugin extends UIPlugin {
58368
58590
  // Export
58369
58591
  // ---------------------------------------------------------------------------
58370
58592
  exportForExcel(data) {
58593
+ for (const sheet of data.sheets) {
58594
+ sheet.cellValues = {};
58595
+ }
58371
58596
  for (const position of this.evaluator.getEvaluatedPositions()) {
58372
58597
  const evaluatedCell = this.evaluator.getEvaluatedCell(position);
58373
58598
  const xc = toXC(position.col, position.row);
58374
58599
  const value = evaluatedCell.value;
58375
58600
  let isFormula = false;
58376
58601
  let newContent = undefined;
58377
- let newFormat = undefined;
58378
58602
  let isExported = true;
58379
58603
  const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
58380
58604
  const formulaCell = this.getCorrespondingFormulaCell(position);
58381
58605
  if (formulaCell) {
58382
58606
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
58383
58607
  isFormula = isExported;
58384
- if (!isExported) {
58385
- // If the cell contains a non-exported formula and that is evaluates to
58386
- // nothing* ,we don't export it.
58387
- // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
58388
- // the empty string.
58389
- if (value !== "") {
58390
- newContent = (value ?? "").toString();
58391
- newFormat = evaluatedCell.format;
58608
+ // If the cell contains a non-exported formula and that is evaluates to
58609
+ // nothing* ,we don't export it.
58610
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
58611
+ // the empty string.
58612
+ if (!isExported && value !== "") {
58613
+ newContent = (value ?? "").toString();
58614
+ const newFormat = evaluatedCell.format;
58615
+ if (newFormat) {
58616
+ const newFormatId = getItemId(newFormat, data.formats);
58617
+ exportedSheetData.formats[xc] = newFormatId;
58392
58618
  }
58393
58619
  }
58394
58620
  }
58395
- const exportedCellData = exportedSheetData.cells[xc] || {};
58396
- const format = newFormat
58397
- ? getItemId(newFormat, data.formats)
58398
- : exportedCellData.format;
58621
+ const exportedCellData = exportedSheetData.cells[xc];
58399
58622
  let content;
58400
58623
  if (isExported && isFormula && formulaCell instanceof FormulaCellWithDependencies) {
58401
58624
  content = formulaCell.contentWithFixedReferences;
58402
58625
  }
58403
58626
  else {
58404
- content = !isExported ? newContent : exportedCellData.content;
58627
+ content = !isExported ? newContent : exportedCellData;
58405
58628
  }
58406
- exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
58629
+ exportedSheetData.cells[xc] = content;
58630
+ exportedSheetData.cellValues[xc] = value;
58407
58631
  }
58408
58632
  }
58409
58633
  /**
@@ -64457,12 +64681,9 @@ class FilterEvaluationPlugin extends UIPlugin {
64457
64681
  const headerString = this.getters.getEvaluatedCell(position).formattedValue;
64458
64682
  const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
64459
64683
  headerNames.push(headerName);
64460
- sheetData.cells[toXC(position.col, position.row)] = {
64461
- ...sheetData.cells[toXC(position.col, position.row)],
64462
- content: headerName,
64463
- value: headerName,
64464
- isFormula: false,
64465
- };
64684
+ const xc = toXC(position.col, position.row);
64685
+ sheetData.cells[xc] = headerName;
64686
+ sheetData.cellValues[xc] = headerName;
64466
64687
  }
64467
64688
  tableData.filters = filters;
64468
64689
  }
@@ -71029,18 +71250,17 @@ function numberRef(reference) {
71029
71250
  `;
71030
71251
  }
71031
71252
 
71032
- function addFormula(cell) {
71033
- const formula = cell.content;
71253
+ function addFormula(formula, value) {
71034
71254
  if (!formula) {
71035
71255
  return { attrs: [], node: escapeXml `` };
71036
71256
  }
71037
- const type = getCellType(cell.value);
71257
+ const type = getCellType(value);
71038
71258
  if (type === undefined) {
71039
71259
  return { attrs: [], node: escapeXml `` };
71040
71260
  }
71041
71261
  const attrs = [["t", type]];
71042
71262
  const XlsxFormula = adaptFormulaToExcel(formula);
71043
- const exportedValue = adaptFormulaValueToExcel(cell.value);
71263
+ const exportedValue = adaptFormulaValueToExcel(value);
71044
71264
  const node = escapeXml /*xml*/ `<f>${XlsxFormula}</f><v>${exportedValue}</v>`;
71045
71265
  return { attrs, node };
71046
71266
  }
@@ -71809,7 +72029,7 @@ function addTableColumns(table, sheetData) {
71809
72029
  const columns = [];
71810
72030
  for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
71811
72031
  const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
71812
- const colName = sheetData.cells[colHeaderXc]?.content || `col${i}`;
72032
+ const colName = sheetData.cells[colHeaderXc] || `col${i}`;
71813
72033
  const colAttributes = [
71814
72034
  ["id", i + 1], // id cannot be 0
71815
72035
  ["name", colName],
@@ -71818,7 +72038,7 @@ function addTableColumns(table, sheetData) {
71818
72038
  // Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
71819
72039
  // `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
71820
72040
  const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
71821
- const colTotalContent = sheetData.cells[colTotalXc]?.content;
72041
+ const colTotalContent = sheetData.cells[colTotalXc];
71822
72042
  if (colTotalContent?.startsWith("=")) {
71823
72043
  colAttributes.push(["totalsRowFunction", "custom"]);
71824
72044
  }
@@ -71889,14 +72109,22 @@ function addRows(construct, data, sheet) {
71889
72109
  if (row.collapsed) {
71890
72110
  rowAttrs.push(["collapsed", 1]);
71891
72111
  }
72112
+ const styles = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.styles));
72113
+ const borders = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.borders));
72114
+ const formats = new PositionMap(iterateItemIdsPositions(sheet.id, sheet.formats));
71892
72115
  const cellNodes = [];
71893
72116
  for (let c = 0; c < sheet.colNumber; c++) {
71894
72117
  const xc = toXC(c, r);
71895
- const cell = sheet.cells[xc];
71896
- if (cell) {
72118
+ const content = sheet.cells[xc];
72119
+ const value = sheet.cellValues[xc];
72120
+ const position = { sheetId: sheet.id, col: c, row: r };
72121
+ const styleId = styles.get(position);
72122
+ const formatId = formats.get(position);
72123
+ const borderId = borders.get(position);
72124
+ if (content || styleId || formatId || borderId || value !== undefined) {
71897
72125
  const attributes = [["r", xc]];
71898
72126
  // style
71899
- const id = normalizeStyle(construct, extractStyle(cell, data));
72127
+ const id = normalizeStyle(construct, extractStyle(data, styleId, formatId, borderId));
71900
72128
  // don't add style if default
71901
72129
  if (id) {
71902
72130
  attributes.push(["s", id]);
@@ -71904,22 +72132,22 @@ function addRows(construct, data, sheet) {
71904
72132
  let additionalAttrs = [];
71905
72133
  let cellNode = escapeXml ``;
71906
72134
  // Either formula or static value inside the cell
71907
- if (cell.isFormula) {
71908
- const res = addFormula(cell);
72135
+ if (content?.startsWith("=") && value !== undefined) {
72136
+ const res = addFormula(content, value);
71909
72137
  if (!res) {
71910
72138
  continue;
71911
72139
  }
71912
72140
  ({ attrs: additionalAttrs, node: cellNode } = res);
71913
72141
  }
71914
- else if (cell.content && isMarkdownLink(cell.content)) {
71915
- const { label } = parseMarkdownLink(cell.content);
72142
+ else if (content && isMarkdownLink(content)) {
72143
+ const { label } = parseMarkdownLink(content);
71916
72144
  ({ attrs: additionalAttrs, node: cellNode } = addContent(label, construct.sharedStrings));
71917
72145
  }
71918
- else if (cell.content && cell.content !== "") {
72146
+ else if (content && content !== "") {
71919
72147
  const isTableHeader = isCellTableHeader(c, r, sheet);
71920
72148
  const isTableTotal = isCellTableTotal(c, r, sheet);
71921
- const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
71922
- ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
72149
+ const isPlainText = !!(formatId && isTextFormat(data.formats[formatId]));
72150
+ ({ attrs: additionalAttrs, node: cellNode } = addContent(content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
71923
72151
  }
71924
72152
  attributes.push(...additionalAttrs);
71925
72153
  // prettier-ignore
@@ -71968,7 +72196,7 @@ function addHyperlinks(construct, data, sheetIndex) {
71968
72196
  const cells = sheet.cells;
71969
72197
  const linkNodes = [];
71970
72198
  for (const xc in cells) {
71971
- const content = cells[xc]?.content;
72199
+ const content = cells[xc];
71972
72200
  if (content && isMarkdownLink(content)) {
71973
72201
  const { label, url } = parseMarkdownLink(content);
71974
72202
  if (isSheetUrl(url)) {
@@ -72940,14 +73168,6 @@ const helpers = {
72940
73168
  createEmptyWorkbookData,
72941
73169
  createEmptySheet,
72942
73170
  createEmptyExcelSheet,
72943
- getDefaultChartJsRuntime,
72944
- getCustomLegendLabels,
72945
- chartFontColor,
72946
- getChartAxisTitleRuntime,
72947
- getChartAxisType,
72948
- getTrendDatasetForBarChart,
72949
- getTrendDatasetForLineChart,
72950
- getFillingMode,
72951
73171
  rgbaToHex,
72952
73172
  colorToRGBA,
72953
73173
  positionToZone,
@@ -72982,7 +73202,6 @@ const helpers = {
72982
73202
  createPivotFormula,
72983
73203
  areDomainArgsFieldsValid,
72984
73204
  splitReference,
72985
- formatTickValue,
72986
73205
  };
72987
73206
  const links = {
72988
73207
  isMarkdownLink,
@@ -73067,15 +73286,12 @@ const constants = {
73067
73286
  DEFAULT_LOCALE,
73068
73287
  HIGHLIGHT_COLOR,
73069
73288
  PIVOT_TABLE_CONFIG,
73070
- TREND_LINE_XAXIS_ID,
73071
- CHART_AXIS_CHOICES,
73072
- INTERACTIVE_LEGEND_CONFIG,
73073
- ChartTerms,
73074
73289
  };
73290
+ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73075
73291
 
73076
- export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
73292
+ export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
73077
73293
 
73078
73294
 
73079
- __info__.version = "18.1.0-alpha.3";
73080
- __info__.date = "2024-11-08T12:18:22.841Z";
73081
- __info__.hash = "96dcfab";
73295
+ __info__.version = "18.1.0-alpha.4";
73296
+ __info__.date = "2024-11-13T15:06:47.769Z";
73297
+ __info__.hash = "e1ad985";