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