@odoo/o-spreadsheet 16.5.0-alpha.3 → 16.5.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.
@@ -3474,14 +3474,15 @@ class ChartJsComponent extends Component {
3474
3474
  createChart(chartData) {
3475
3475
  const canvas = this.canvas.el;
3476
3476
  const ctx = canvas.getContext("2d");
3477
+ // @ts-ignore
3477
3478
  this.chart = new window.Chart(ctx, chartData);
3478
3479
  }
3479
3480
  updateChartJs(chartRuntime) {
3480
3481
  const chartData = chartRuntime.chartJsConfig;
3481
3482
  if (chartData.data && chartData.data.datasets) {
3482
3483
  this.chart.data = chartData.data;
3483
- if (chartData.options?.title) {
3484
- this.chart.config.options.title = chartData.options.title;
3484
+ if (chartData.options?.plugins?.title) {
3485
+ this.chart.config.options.plugins.title = chartData.options.plugins.title;
3485
3486
  }
3486
3487
  if (chartData.options && "valueLabel" in chartData.options) {
3487
3488
  if (chartData.options?.valueLabel) {
@@ -3491,12 +3492,13 @@ class ChartJsComponent extends Component {
3491
3492
  }
3492
3493
  }
3493
3494
  else {
3494
- this.chart.data.datasets = undefined;
3495
+ this.chart.data.datasets = [];
3495
3496
  }
3496
- this.chart.config.options.tooltips = chartData.options?.tooltips;
3497
- this.chart.config.options.legend = chartData.options?.legend;
3497
+ this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
3498
+ this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
3498
3499
  this.chart.config.options.scales = chartData.options?.scales;
3499
- this.chart.update({ duration: 0 });
3500
+ // ?
3501
+ this.chart.update("active");
3500
3502
  }
3501
3503
  }
3502
3504
  ChartJsComponent.props = {
@@ -16178,49 +16180,40 @@ function truncateLabel(label) {
16178
16180
  * Get a default chart js configuration
16179
16181
  */
16180
16182
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
16181
- return {
16182
- type: chart.type,
16183
- options: {
16184
- // https://www.chartjs.org/docs/latest/general/responsive.html
16185
- responsive: true,
16186
- maintainAspectRatio: false,
16187
- layout: {
16188
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
16189
- },
16190
- elements: {
16191
- line: {
16192
- fill: false, // do not fill the area under line charts
16193
- },
16194
- point: {
16195
- hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
16196
- },
16197
- },
16198
- animation: {
16199
- duration: 0, // general animation time
16183
+ const options = {
16184
+ // https://www.chartjs.org/docs/latest/general/responsive.html
16185
+ responsive: true,
16186
+ maintainAspectRatio: false,
16187
+ layout: {
16188
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
16189
+ },
16190
+ elements: {
16191
+ line: {
16192
+ fill: false, // do not fill the area under line charts
16200
16193
  },
16201
- hover: {
16202
- animationDuration: 10, // duration of animations when hovering an item
16194
+ point: {
16195
+ hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
16203
16196
  },
16204
- responsiveAnimationDuration: 0,
16197
+ },
16198
+ animation: false,
16199
+ plugins: {
16205
16200
  title: {
16206
16201
  display: !!chart.title,
16207
- fontSize: 22,
16208
- fontStyle: "normal",
16209
16202
  text: _t(chart.title),
16210
- fontColor,
16203
+ color: fontColor,
16204
+ font: { size: 22, weight: "normal" },
16211
16205
  },
16212
16206
  legend: {
16213
16207
  // Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.
16214
16208
  // If we want to re-enable this in the future, we need to override the default onClick to stop the event propagation
16215
- onClick: undefined,
16209
+ onClick: () => { },
16216
16210
  },
16217
- tooltips: {
16211
+ tooltip: {
16218
16212
  callbacks: {
16219
- label: function (tooltipItem, data) {
16220
- let xLabel = data.datasets?.[tooltipItem.datasetIndex || 0]?.label;
16221
- const yLabel = tooltipItem.yLabel !== ""
16222
- ? tooltipItem.yLabel
16223
- : data.datasets?.[tooltipItem.datasetIndex || 0]?.data?.[tooltipItem.index || 0];
16213
+ label: function (tooltipItem) {
16214
+ let xLabel = tooltipItem.label;
16215
+ // tooltipItem.parsed.y can be an object or a number for pie charts
16216
+ const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
16224
16217
  const yLabelStr = format && typeof yLabel === "number"
16225
16218
  ? formatValue(yLabel, { format, locale })
16226
16219
  : yLabel?.toLocaleString() || "";
@@ -16229,10 +16222,15 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
16229
16222
  },
16230
16223
  },
16231
16224
  },
16225
+ };
16226
+ return {
16227
+ type: chart.type,
16228
+ options,
16232
16229
  data: {
16233
16230
  labels: labels.map(truncateLabel),
16234
16231
  datasets: [],
16235
16232
  },
16233
+ plugins: [],
16236
16234
  };
16237
16235
  }
16238
16236
  function getChartLabelFormat(getters, range) {
@@ -16429,7 +16427,7 @@ function getBarConfiguration(chart, labels, localeFormat) {
16429
16427
  const fontColor = chartFontColor(chart.background);
16430
16428
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
16431
16429
  const legend = {
16432
- labels: { fontColor },
16430
+ labels: { color: fontColor },
16433
16431
  };
16434
16432
  if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
16435
16433
  legend.display = false;
@@ -16437,42 +16435,35 @@ function getBarConfiguration(chart, labels, localeFormat) {
16437
16435
  else {
16438
16436
  legend.position = chart.legendPosition;
16439
16437
  }
16440
- config.options.legend = { ...config.options?.legend, ...legend };
16438
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
16441
16439
  config.options.layout = {
16442
16440
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
16443
16441
  };
16444
16442
  config.options.scales = {
16445
- xAxes: [
16446
- {
16447
- ticks: {
16448
- // x axis configuration
16449
- maxRotation: 60,
16450
- minRotation: 15,
16451
- padding: 5,
16452
- labelOffset: 2,
16453
- fontColor,
16454
- },
16443
+ x: {
16444
+ ticks: {
16445
+ padding: 5,
16446
+ color: fontColor,
16455
16447
  },
16456
- ],
16457
- yAxes: [
16458
- {
16459
- position: chart.verticalAxisPosition,
16460
- ticks: {
16461
- fontColor,
16462
- // y axis configuration
16463
- beginAtZero: true,
16464
- callback: (value) => {
16465
- return localeFormat.format
16466
- ? formatValue(value, localeFormat)
16467
- : value?.toLocaleString() || value;
16468
- },
16448
+ },
16449
+ y: {
16450
+ position: chart.verticalAxisPosition,
16451
+ beginAtZero: true,
16452
+ ticks: {
16453
+ color: fontColor,
16454
+ callback: (value) => {
16455
+ return localeFormat.format
16456
+ ? formatValue(value, localeFormat)
16457
+ : value?.toLocaleString() || value;
16469
16458
  },
16470
16459
  },
16471
- ],
16460
+ },
16472
16461
  };
16473
16462
  if (chart.stacked) {
16474
- config.options.scales.xAxes[0].stacked = true;
16475
- config.options.scales.yAxes[0].stacked = true;
16463
+ // @ts-ignore chart.js type is broken
16464
+ config.options.scales.x.stacked = true;
16465
+ // @ts-ignore chart.js type is broken
16466
+ config.options.scales.y.stacked = true;
16476
16467
  }
16477
16468
  return config;
16478
16469
  }
@@ -16676,17 +16667,18 @@ function getGaugeConfiguration(chart, locale) {
16676
16667
  padding: { left: 30, right: 30, top: chart.title ? 10 : 25, bottom: 25 },
16677
16668
  };
16678
16669
  config.options.needle = {
16679
- radiusPercentage: 2,
16680
- widthPercentage: 3.2,
16681
- lengthPercentage: 80,
16682
- color: "#000000",
16670
+ width: 10,
16671
+ borderColor: "#000000",
16672
+ backgroundColor: "#000000",
16683
16673
  };
16684
16674
  config.options.valueLabel = {
16685
16675
  display: false,
16686
- formatter: null,
16687
- color: "#FFFFFF",
16676
+ font: {
16677
+ size: 30,
16678
+ color: "#FFFFFF",
16679
+ },
16688
16680
  backgroundColor: "#000000",
16689
- fontSize: 30,
16681
+ borderColor: "#000000",
16690
16682
  borderRadius: 5,
16691
16683
  padding: {
16692
16684
  top: 5,
@@ -16694,7 +16686,6 @@ function getGaugeConfiguration(chart, locale) {
16694
16686
  bottom: 5,
16695
16687
  left: 5,
16696
16688
  },
16697
- bottomMarginPercentage: 5,
16698
16689
  };
16699
16690
  return config;
16700
16691
  }
@@ -16736,16 +16727,12 @@ function createGaugeChartRuntime(chart, getters) {
16736
16727
  data.push(point.value);
16737
16728
  backgroundColor.push(point.color);
16738
16729
  });
16739
- // There's a bug in gauge lib when the last element in `data` is 0 (i.e. when the range maximum is 0).
16740
- // The value wrongly fallbacks to 1 because 0 is falsy
16741
- // See https://github.com/haiiaaa/chartjs-gauge/pull/33
16742
- // https://github.com/haiiaaa/chartjs-gauge/blob/2ea50541d754d710cb30c2502fa690ac5dc27afd/src/controllers/controller.gauge.js#L52
16743
16730
  data.push(maxNeedleValue);
16744
16731
  backgroundColor.push(colors.upperColor);
16745
16732
  const dataRange = chart.dataRange;
16746
16733
  const deltaBeyondRangeLimit = needleCoverage / 30;
16747
16734
  let needleValue = minNeedleValue - deltaBeyondRangeLimit; // make needle value always at the minimum by default
16748
- let cellFormatter = null;
16735
+ let cellFormatter = undefined;
16749
16736
  let displayValue = false;
16750
16737
  if (dataRange !== undefined) {
16751
16738
  const cell = getters.getEvaluatedCell({
@@ -16760,6 +16747,7 @@ function createGaugeChartRuntime(chart, getters) {
16760
16747
  // smaller and slightly larger than minRange and maxRange to mark the fact
16761
16748
  // that the needle is out of the range limits
16762
16749
  needleValue = clip(cell.value, minNeedleValue - deltaBeyondRangeLimit, maxNeedleValue + deltaBeyondRangeLimit);
16750
+ // show the original value, not the clipped one
16763
16751
  cellFormatter = () => getters.getRangeFormattedValues(dataRange)[0];
16764
16752
  displayValue = true;
16765
16753
  }
@@ -16807,31 +16795,29 @@ const Milliseconds = {
16807
16795
  },
16808
16796
  };
16809
16797
  /**
16810
- * Regex to test if a format string is a date format that can be translated into a moment time format
16798
+ * Regex to test if a format string is a date format that can be translated into a luxon time format
16811
16799
  */
16812
- const timeFormatMomentCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
16800
+ const timeFormatLuxonCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
16813
16801
  /** Get the time options for the XAxis of ChartJS */
16814
16802
  function getChartTimeOptions(labels, labelFormat, locale) {
16815
- const momentFormat = convertDateFormatForMoment(labelFormat);
16816
- const timeUnit = getBestTimeUnitForScale(labels, momentFormat, locale);
16803
+ const luxonFormat = convertDateFormatForLuxon(labelFormat);
16804
+ const timeUnit = getBestTimeUnitForScale(labels, luxonFormat, locale);
16817
16805
  const displayFormats = {};
16818
16806
  if (timeUnit) {
16819
- displayFormats[timeUnit] = momentFormat;
16807
+ displayFormats[timeUnit] = luxonFormat;
16820
16808
  }
16821
16809
  return {
16822
- parser: momentFormat,
16810
+ parser: luxonFormat,
16823
16811
  displayFormats,
16824
- unit: timeUnit,
16812
+ unit: timeUnit ?? false,
16825
16813
  };
16826
16814
  }
16827
16815
  /**
16828
16816
  * Convert the given date format into a format that moment.js understands.
16829
16817
  *
16830
- * https://momentjs.com/docs/#/parsing/string-format/
16818
+ * https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens
16831
16819
  */
16832
- function convertDateFormatForMoment(format) {
16833
- format = format.replace(/y/g, "Y");
16834
- format = format.replace(/d/g, "D");
16820
+ function convertDateFormatForLuxon(format) {
16835
16821
  // "m" before "h" === month, "m" after "h" === minute
16836
16822
  const indexH = format.indexOf("h");
16837
16823
  if (indexH >= 0) {
@@ -16902,6 +16888,8 @@ function getBestTimeUnitForScale(labels, format, locale) {
16902
16888
  return "year";
16903
16889
  }
16904
16890
 
16891
+ // @ts-ignore
16892
+ const Chart = window.Chart;
16905
16893
  class LineChart extends AbstractChart {
16906
16894
  dataSets;
16907
16895
  labelRange;
@@ -17031,7 +17019,7 @@ function canChartParseLabels(labelRange, getters) {
17031
17019
  return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);
17032
17020
  }
17033
17021
  function getChartAxisType(chart, getters) {
17034
- if (isDateChart(chart, getters)) {
17022
+ if (isDateChart(chart, getters) && isLuxonTimeAdapterInstalled()) {
17035
17023
  return "time";
17036
17024
  }
17037
17025
  if (isLinearChart(chart, getters)) {
@@ -17054,7 +17042,7 @@ function canBeDateChart(labelRange, getters) {
17054
17042
  col: labelRange.zone.left,
17055
17043
  row: labelRange.zone.top,
17056
17044
  }).format;
17057
- return Boolean(labelFormat && timeFormatMomentCompatible.test(labelFormat));
17045
+ return Boolean(labelFormat && timeFormatLuxonCompatible.test(labelFormat));
17058
17046
  }
17059
17047
  function canBeLinearChart(labelRange, getters) {
17060
17048
  if (!labelRange) {
@@ -17074,10 +17062,11 @@ function getLineConfiguration(chart, labels, localeFormat) {
17074
17062
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
17075
17063
  const legend = {
17076
17064
  labels: {
17077
- fontColor,
17065
+ color: fontColor,
17078
17066
  generateLabels(chart) {
17067
+ // color the legend labels with the dataset color, without any transparency
17079
17068
  const { data } = chart;
17080
- const labels = window.Chart.defaults.global.legend.labels.generateLabels(chart);
17069
+ const labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
17081
17070
  for (const [index, label] of labels.entries()) {
17082
17071
  label.fillStyle = data.datasets[index].borderColor;
17083
17072
  }
@@ -17091,41 +17080,33 @@ function getLineConfiguration(chart, labels, localeFormat) {
17091
17080
  else {
17092
17081
  legend.position = chart.legendPosition;
17093
17082
  }
17094
- config.options.legend = { ...config.options?.legend, ...legend };
17083
+ Object.assign(config.options.plugins.legend || {}, legend);
17095
17084
  config.options.layout = {
17096
17085
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
17097
17086
  };
17098
17087
  config.options.scales = {
17099
- xAxes: [
17100
- {
17101
- ticks: {
17102
- // x axis configuration
17103
- maxRotation: 60,
17104
- minRotation: 15,
17105
- padding: 5,
17106
- labelOffset: 2,
17107
- fontColor,
17108
- },
17088
+ x: {
17089
+ ticks: {
17090
+ padding: 5,
17091
+ color: fontColor,
17109
17092
  },
17110
- ],
17111
- yAxes: [
17112
- {
17113
- position: chart.verticalAxisPosition,
17114
- ticks: {
17115
- fontColor,
17116
- // y axis configuration
17117
- beginAtZero: true,
17118
- callback: (value) => {
17119
- return localeFormat.format
17120
- ? formatValue(value, localeFormat)
17121
- : value?.toLocaleString() || value;
17122
- },
17093
+ },
17094
+ y: {
17095
+ position: chart.verticalAxisPosition,
17096
+ beginAtZero: true,
17097
+ ticks: {
17098
+ color: fontColor,
17099
+ callback: (value) => {
17100
+ return localeFormat.format
17101
+ ? formatValue(value, localeFormat)
17102
+ : value?.toLocaleString() || value;
17123
17103
  },
17124
17104
  },
17125
- ],
17105
+ },
17126
17106
  };
17127
- if (chart.stacked) {
17128
- config.options.scales.yAxes[0].stacked = true;
17107
+ if (chart.stacked && config.options?.scales?.y) {
17108
+ // @ts-ignore chart.js type is wrong
17109
+ config.options.scales.y.stacked = true;
17129
17110
  }
17130
17111
  return config;
17131
17112
  }
@@ -17152,15 +17133,18 @@ function createLineChartRuntime(chart, getters) {
17152
17133
  const config = getLineConfiguration(chart, labels, localeFormat);
17153
17134
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
17154
17135
  if (axisType === "time") {
17155
- config.options.scales.xAxes[0].type = "time";
17156
- config.options.scales.xAxes[0].time = getChartTimeOptions(labels, labelFormat, locale);
17157
- config.options.scales.xAxes[0].ticks.maxTicksLimit = 15;
17136
+ const axis = {
17137
+ type: "time",
17138
+ time: getChartTimeOptions(labels, labelFormat, locale),
17139
+ };
17140
+ Object.assign(config.options.scales.x, axis);
17141
+ config.options.scales.x.ticks.maxTicksLimit = 15;
17158
17142
  }
17159
17143
  else if (axisType === "linear") {
17160
- config.options.scales.xAxes[0].type = "linear";
17161
- config.options.scales.xAxes[0].ticks.callback = (value) => formatValue(value, localeFormat);
17162
- config.options.tooltips.callbacks.title = (tooltipItem) => {
17163
- return formatValue(tooltipItem[0]?.xLabel || "", localeFormat);
17144
+ config.options.scales.x.type = "linear";
17145
+ config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
17146
+ config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
17147
+ return formatValue(tooltipItem[0]?.label || "", localeFormat);
17164
17148
  };
17165
17149
  }
17166
17150
  const colors = new ChartColors();
@@ -17178,7 +17162,7 @@ function createLineChartRuntime(chart, getters) {
17178
17162
  const dataset = {
17179
17163
  label,
17180
17164
  data,
17181
- lineTension: 0,
17165
+ tension: 0,
17182
17166
  borderColor: color,
17183
17167
  backgroundColor,
17184
17168
  pointBackgroundColor: color,
@@ -17187,6 +17171,22 @@ function createLineChartRuntime(chart, getters) {
17187
17171
  config.data.datasets.push(dataset);
17188
17172
  }
17189
17173
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
17174
+ }
17175
+ let missingTimeAdapterAlreadyWarned = false;
17176
+ function isLuxonTimeAdapterInstalled() {
17177
+ // @ts-ignore
17178
+ if (!window.Chart) {
17179
+ return false;
17180
+ }
17181
+ // @ts-ignore
17182
+ const adapter = new window.Chart._adapters._date({});
17183
+ // @ts-ignore
17184
+ const isInstalled = adapter._id === "luxon";
17185
+ if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
17186
+ missingTimeAdapterAlreadyWarned = true;
17187
+ console.warn("'chartjs-adapter-luxon' time adapter is not installed. Time scale axes are disabled.");
17188
+ }
17189
+ return isInstalled;
17190
17190
  }
17191
17191
 
17192
17192
  class PieChart extends AbstractChart {
@@ -17291,7 +17291,7 @@ function getPieConfiguration(chart, labels, localeFormat) {
17291
17291
  const fontColor = chartFontColor(chart.background);
17292
17292
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
17293
17293
  const legend = {
17294
- labels: { fontColor },
17294
+ labels: { color: fontColor },
17295
17295
  };
17296
17296
  if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
17297
17297
  legend.display = false;
@@ -17299,12 +17299,12 @@ function getPieConfiguration(chart, labels, localeFormat) {
17299
17299
  else {
17300
17300
  legend.position = chart.legendPosition;
17301
17301
  }
17302
- config.options.legend = { ...config.options?.legend, ...legend };
17302
+ Object.assign(config.options.plugins.legend || {}, legend);
17303
17303
  config.options.layout = {
17304
17304
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
17305
17305
  };
17306
- config.options.tooltips.callbacks.title = function (tooltipItems, data) {
17307
- return data.datasets[tooltipItems[0].datasetIndex].label;
17306
+ config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
17307
+ return tooltipItems[0].dataset.label;
17308
17308
  };
17309
17309
  return config;
17310
17310
  }
@@ -18068,6 +18068,278 @@ function getSmartChartDefinition(zone, getters) {
18068
18068
  };
18069
18069
  }
18070
18070
 
18071
+ // @ts-ignore
18072
+ if (window.Chart) {
18073
+ // @ts-ignore
18074
+ const DoughnutController = window.Chart?.DoughnutController;
18075
+ /**
18076
+ * Example :
18077
+ * const chart = new Chart(ctx, {
18078
+ * type: "gauge",
18079
+ * data: {
18080
+ * datasets: [
18081
+ * {
18082
+ * borderWidth: 5,
18083
+ * data: [10, 20, 10],
18084
+ * value: 23,
18085
+ * backgroundColor: [
18086
+ * "#dc3545", //red
18087
+ * "#ffc107", //orange
18088
+ * "#28a745", //green
18089
+ * ],
18090
+ * },
18091
+ * ],
18092
+ * },
18093
+ * options: {
18094
+ * plugins: {
18095
+ * title: {
18096
+ * display: true,
18097
+ * text: "Custom Chart Title",
18098
+ * padding: {
18099
+ * top: 10,
18100
+ * bottom: 30,
18101
+ * },
18102
+ * font : {
18103
+ * size : 30
18104
+ * }
18105
+ * },
18106
+ * },
18107
+ * valueLabel: {
18108
+ * display: true,
18109
+ * format: (value) => {
18110
+ * return value.toFixed(2) + "%";
18111
+ * },
18112
+ * font : {
18113
+ * size : 35,
18114
+ * family : "Arial",
18115
+ * color : "#FC0",
18116
+ * },
18117
+ * backgroundColor : "#ccc",
18118
+ * borderColor : "#090",
18119
+ * borderRadius : 50
18120
+ * },
18121
+ * needle: {
18122
+ * display: true,
18123
+ * width: 50,
18124
+ * color: "#DC5",
18125
+ * backgroundColor : "#ccc",
18126
+ * },
18127
+ * },
18128
+ * });
18129
+ */
18130
+ class GaugeController extends DoughnutController {
18131
+ static id = "gauge";
18132
+ static defaults = {
18133
+ ...DoughnutController.defaults,
18134
+ circumference: 180,
18135
+ rotation: 270,
18136
+ };
18137
+ static overrides = {
18138
+ aspectRatio: 2,
18139
+ plugins: {
18140
+ legend: {
18141
+ display: false,
18142
+ },
18143
+ tooltip: {
18144
+ enabled: false,
18145
+ },
18146
+ },
18147
+ };
18148
+ get chartHeight() {
18149
+ return Math.abs(this.chart.chartArea.top - this.chart.chartArea.bottom);
18150
+ }
18151
+ get chartWidth() {
18152
+ return Math.abs(this.chart.chartArea.left - this.chart.chartArea.right);
18153
+ }
18154
+ get needleOptions() {
18155
+ const { config } = this.chart;
18156
+ const options = config.options;
18157
+ // @ts-ignore
18158
+ return options?.needle || {};
18159
+ }
18160
+ get valueLabelOptions() {
18161
+ const { config } = this.chart;
18162
+ const options = config.options;
18163
+ // @ts-ignore
18164
+ return options?.valueLabel || {};
18165
+ }
18166
+ get minValue() {
18167
+ const dataset = this.getDataset();
18168
+ // @ts-ignore
18169
+ return dataset.minValue || 0;
18170
+ }
18171
+ getValueAngleInPercent(value) {
18172
+ const dataset = this.getDataset();
18173
+ const data = dataset.data || [];
18174
+ const max = Math.max(...data);
18175
+ const min = this.minValue;
18176
+ return (value - min) / (max - min);
18177
+ }
18178
+ drawValueLabel(params) {
18179
+ const { ctx } = this.chart;
18180
+ if (this.valueLabelOptions.display === false) {
18181
+ return;
18182
+ }
18183
+ // draw background rectangle
18184
+ ctx.save();
18185
+ ctx.beginPath();
18186
+ ctx.fillStyle = params.valueLabel.backgroundColor;
18187
+ ctx.strokeStyle = params.valueLabel.borderColor;
18188
+ ctx.roundRect(params.valueLabel.rect.x, params.valueLabel.rect.y, params.valueLabel.rect.width, params.valueLabel.rect.height, params.valueLabel.borderRadius);
18189
+ ctx.stroke();
18190
+ ctx.fill();
18191
+ // draw value text
18192
+ ctx.textAlign = "center";
18193
+ ctx.textBaseline = "middle";
18194
+ ctx.font = params.valueLabel.font;
18195
+ ctx.fillStyle = params.valueLabel.textColor;
18196
+ ctx.fillText(params.valueLabel.valueText, params.valueLabel.textPosition.x, params.valueLabel.textPosition.y);
18197
+ ctx.restore();
18198
+ }
18199
+ drawNeedle(params) {
18200
+ const { ctx } = this.chart;
18201
+ if (this.needleOptions.display === false) {
18202
+ return;
18203
+ }
18204
+ // translate & rotate next paths
18205
+ ctx.save();
18206
+ ctx.translate(params.needle.position.x, params.needle.position.y);
18207
+ ctx.rotate(-Math.PI / 2 + Math.PI * params.needle.percent);
18208
+ // draw circle
18209
+ ctx.beginPath();
18210
+ ctx.fillStyle = params.needle.backgroundColor;
18211
+ ctx.strokeStyle = params.needle.borderColor;
18212
+ ctx.ellipse(0, 0, params.needle.width / 2, params.needle.width / 2, 0, 0, 2 * Math.PI);
18213
+ ctx.fill();
18214
+ ctx.stroke();
18215
+ ctx.closePath();
18216
+ // draw needle
18217
+ ctx.fillStyle = params.needle.backgroundColor;
18218
+ ctx.strokeStyle = params.needle.borderColor;
18219
+ ctx.beginPath();
18220
+ ctx.moveTo(-params.needle.width / 2, 0);
18221
+ ctx.lineTo(0, -params.needle.height + 10);
18222
+ ctx.lineTo(+params.needle.width / 2, 0);
18223
+ ctx.closePath();
18224
+ ctx.fill();
18225
+ ctx.stroke();
18226
+ ctx.restore();
18227
+ }
18228
+ computeRenderingParams() {
18229
+ const { ctx, config } = this.chart;
18230
+ const options = config.options;
18231
+ options.layout = options.layout || {};
18232
+ options.layout.padding = options.layout.padding || {};
18233
+ const formatter = this.valueLabelOptions.formatter;
18234
+ const fmt = typeof formatter === "function" ? formatter : (value) => value;
18235
+ const dataset = this.getDataset();
18236
+ // @ts-ignore value is a custom property for gauge charts
18237
+ const value = dataset.value || 0;
18238
+ const valueText = fmt(value).toString();
18239
+ const percent = this.getValueAngleInPercent(value);
18240
+ const fontFamily = this.valueLabelOptions.font?.family || "Arial";
18241
+ const optionsFontSize = this.valueLabelOptions.font?.size || 0;
18242
+ const fontSize = optionsFontSize >= 1 ? optionsFontSize : 30;
18243
+ const font = `${fontSize}px ${fontFamily}`;
18244
+ const padding = {
18245
+ right: 0,
18246
+ left: 0,
18247
+ top: 0,
18248
+ bottom: 0,
18249
+ ...this.valueLabelOptions.padding,
18250
+ };
18251
+ padding.left = padding.left >= 0 ? padding.left : 10;
18252
+ padding.right = padding.right >= 0 ? padding.right : 10;
18253
+ padding.top = padding.top >= 0 ? padding.top : 10;
18254
+ padding.bottom = padding.bottom >= 0 ? padding.bottom : 10;
18255
+ const chartArea = this.chart.chartArea;
18256
+ const offsetX = this.offsetX || 0;
18257
+ const offsetY = this.offsetY || 0;
18258
+ const centerX = (chartArea.left + chartArea.right) / 2;
18259
+ const centerY = (chartArea.top + chartArea.bottom) / 2;
18260
+ const center = {
18261
+ x: centerX + offsetX,
18262
+ y: centerY + offsetY,
18263
+ };
18264
+ const textPosition = {
18265
+ x: center.x,
18266
+ y: center.y,
18267
+ };
18268
+ ctx.save();
18269
+ ctx.font = font;
18270
+ const metrics = ctx.measureText(valueText);
18271
+ ctx.restore();
18272
+ const textHeight = metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent;
18273
+ return {
18274
+ needle: {
18275
+ borderColor: this.needleOptions.borderColor || "#000",
18276
+ backgroundColor: this.needleOptions.backgroundColor || "#000",
18277
+ width: this.needleOptions.width || 10,
18278
+ height: Math.min(this.chartHeight, this.chartWidth / 2),
18279
+ position: {
18280
+ x: textPosition.x,
18281
+ y: textPosition.y,
18282
+ },
18283
+ percent,
18284
+ },
18285
+ valueLabel: {
18286
+ textColor: this.valueLabelOptions.font?.color || "#FFF",
18287
+ backgroundColor: this.valueLabelOptions.backgroundColor || "#000",
18288
+ borderColor: this.valueLabelOptions.borderColor || "#000",
18289
+ borderRadius: this.valueLabelOptions.borderRadius || 10,
18290
+ rect: {
18291
+ x: center.x - metrics.width / 2 - padding.left,
18292
+ y: center.y + textHeight / 2 + padding.top,
18293
+ width: metrics.width + padding.left + padding.right,
18294
+ height: -(textHeight + 6 + padding.top + padding.bottom),
18295
+ },
18296
+ valueText,
18297
+ textPosition,
18298
+ textHeight,
18299
+ font,
18300
+ },
18301
+ };
18302
+ }
18303
+ draw() {
18304
+ super.draw();
18305
+ const params = this.computeRenderingParams();
18306
+ this.drawNeedle(params);
18307
+ this.drawValueLabel(params);
18308
+ }
18309
+ updateElements(elements, start, count, mode) {
18310
+ super.updateElements(elements, start, count, mode);
18311
+ const dataset = this.getDataset();
18312
+ const data = this.getDataset().data;
18313
+ // @ts-ignore
18314
+ const minValue = dataset.minValue;
18315
+ // @ts-ignore
18316
+ const rotation = this.chart.options.rotation || 0;
18317
+ // @ts-ignore
18318
+ const circumference = this.chart.options.circumference || 0;
18319
+ for (let arcIndex = 0; arcIndex < data.length; arcIndex++) {
18320
+ const previousValue = arcIndex === 0 ? minValue : data[arcIndex - 1];
18321
+ const startAngleInPercent = this.getValueAngleInPercent(previousValue);
18322
+ const endAngleInPercent = this.getValueAngleInPercent(data[arcIndex]);
18323
+ const startAngle = degreesToRadians(rotation + circumference * startAngleInPercent) - Math.PI / 2;
18324
+ const endAngle = degreesToRadians(rotation + circumference * endAngleInPercent) - Math.PI / 2;
18325
+ const arcCircumference = endAngle - startAngle;
18326
+ const arc = elements[arcIndex];
18327
+ const propertiesUpdates = {
18328
+ startAngle,
18329
+ endAngle,
18330
+ circumference: arcCircumference,
18331
+ };
18332
+ this.updateElement(arc, arcIndex, propertiesUpdates, mode);
18333
+ }
18334
+ }
18335
+ }
18336
+ function degreesToRadians(degrees) {
18337
+ return (degrees * Math.PI) / 180;
18338
+ }
18339
+ // @ts-ignore
18340
+ window.Chart.register(GaugeController);
18341
+ }
18342
+
18071
18343
  //------------------------------------------------------------------------------
18072
18344
  // Helpers
18073
18345
  //------------------------------------------------------------------------------
@@ -36417,7 +36689,8 @@ class EvaluationChartPlugin extends UIPlugin {
36417
36689
  if (!chart) {
36418
36690
  throw new Error(`No chart for the given id: ${figureId}`);
36419
36691
  }
36420
- this.history.update("charts", figureId, this.createRuntimeChart(chart));
36692
+ const runtime = this.createRuntimeChart(chart);
36693
+ this.history.update("charts", figureId, runtime);
36421
36694
  }
36422
36695
  return this.charts[figureId];
36423
36696
  }
@@ -49505,7 +49778,14 @@ function addCellWiseConditionalFormatting(dxfs // cell-wise CF
49505
49778
  `;
49506
49779
  }
49507
49780
 
49508
- const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo name="TableStyleLight8" showFirstColumn="0" showLastColumn="0" showRowStripes="0" showColumnStripes="0"/>`;
49781
+ const TABLE_DEFAULT_ATTRS = [
49782
+ ["name", "TableStyleLight8"],
49783
+ ["showFirstColumn", "0"],
49784
+ ["showLastColumn", "0"],
49785
+ ["showRowStripes", "0"],
49786
+ ["showColumnStripes", "0"],
49787
+ ];
49788
+ const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(TABLE_DEFAULT_ATTRS)}/>`;
49509
49789
  function createTable(table, tableId, sheetData) {
49510
49790
  const tableAttributes = [
49511
49791
  ["id", tableId],
@@ -50590,6 +50870,6 @@ const constants = {
50590
50870
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
50591
50871
 
50592
50872
 
50593
- __info__.version = '16.5.0-alpha.3';
50594
- __info__.date = '2023-08-09T09:07:55.435Z';
50595
- __info__.hash = 'b0e8fd5';
50873
+ __info__.version = '16.5.0-alpha.4';
50874
+ __info__.date = '2023-08-10T09:39:12.478Z';
50875
+ __info__.hash = 'c8db018';