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