[IMP] spreadsheet: update o_spreadsheet to latest version
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/c8db0189 [REL] 16.5.0-alpha.4 https://github.com/odoo/o-spreadsheet/commit/405da6dc [IMP] packages: update chart.js to 4.3 Task: 3412671 https://github.com/odoo/o-spreadsheet/commit/830a144f [FIX] xlsx: use FormatAttributes to export filters Task: 3460544 https://github.com/odoo/o-spreadsheet/commit/63ef23ea [FIX] tests: avoid memory leak Part-of: odoo/odoo#127259
This commit is contained in:
committed by
Pierre Pulinckx (pipu)
parent
6f0348ba62
commit
63480e6895
@@ -3475,14 +3475,15 @@
|
||||
createChart(chartData) {
|
||||
const canvas = this.canvas.el;
|
||||
const ctx = canvas.getContext("2d");
|
||||
// @ts-ignore
|
||||
this.chart = new window.Chart(ctx, chartData);
|
||||
}
|
||||
updateChartJs(chartRuntime) {
|
||||
const chartData = chartRuntime.chartJsConfig;
|
||||
if (chartData.data && chartData.data.datasets) {
|
||||
this.chart.data = chartData.data;
|
||||
if (chartData.options?.title) {
|
||||
this.chart.config.options.title = chartData.options.title;
|
||||
if (chartData.options?.plugins?.title) {
|
||||
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
||||
}
|
||||
if (chartData.options && "valueLabel" in chartData.options) {
|
||||
if (chartData.options?.valueLabel) {
|
||||
@@ -3492,12 +3493,13 @@
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.chart.data.datasets = undefined;
|
||||
this.chart.data.datasets = [];
|
||||
}
|
||||
this.chart.config.options.tooltips = chartData.options?.tooltips;
|
||||
this.chart.config.options.legend = chartData.options?.legend;
|
||||
this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
|
||||
this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
|
||||
this.chart.config.options.scales = chartData.options?.scales;
|
||||
this.chart.update({ duration: 0 });
|
||||
// ?
|
||||
this.chart.update("active");
|
||||
}
|
||||
}
|
||||
ChartJsComponent.props = {
|
||||
@@ -16179,49 +16181,40 @@
|
||||
* Get a default chart js configuration
|
||||
*/
|
||||
function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
|
||||
return {
|
||||
type: chart.type,
|
||||
options: {
|
||||
// https://www.chartjs.org/docs/latest/general/responsive.html
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: {
|
||||
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||
const options = {
|
||||
// https://www.chartjs.org/docs/latest/general/responsive.html
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: {
|
||||
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
fill: false, // do not fill the area under line charts
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
fill: false, // do not fill the area under line charts
|
||||
},
|
||||
point: {
|
||||
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
||||
},
|
||||
point: {
|
||||
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
||||
},
|
||||
animation: {
|
||||
duration: 0, // general animation time
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 10, // duration of animations when hovering an item
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
},
|
||||
animation: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: !!chart.title,
|
||||
fontSize: 22,
|
||||
fontStyle: "normal",
|
||||
text: _t(chart.title),
|
||||
fontColor,
|
||||
color: fontColor,
|
||||
font: { size: 22, weight: "normal" },
|
||||
},
|
||||
legend: {
|
||||
// Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.
|
||||
// If we want to re-enable this in the future, we need to override the default onClick to stop the event propagation
|
||||
onClick: undefined,
|
||||
onClick: () => { },
|
||||
},
|
||||
tooltips: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
let xLabel = data.datasets?.[tooltipItem.datasetIndex || 0]?.label;
|
||||
const yLabel = tooltipItem.yLabel !== ""
|
||||
? tooltipItem.yLabel
|
||||
: data.datasets?.[tooltipItem.datasetIndex || 0]?.data?.[tooltipItem.index || 0];
|
||||
label: function (tooltipItem) {
|
||||
let xLabel = tooltipItem.label;
|
||||
// tooltipItem.parsed.y can be an object or a number for pie charts
|
||||
const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
|
||||
const yLabelStr = format && typeof yLabel === "number"
|
||||
? formatValue(yLabel, { format, locale })
|
||||
: yLabel?.toLocaleString() || "";
|
||||
@@ -16230,10 +16223,15 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
type: chart.type,
|
||||
options,
|
||||
data: {
|
||||
labels: labels.map(truncateLabel),
|
||||
datasets: [],
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
function getChartLabelFormat(getters, range) {
|
||||
@@ -16430,7 +16428,7 @@
|
||||
const fontColor = chartFontColor(chart.background);
|
||||
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
||||
const legend = {
|
||||
labels: { fontColor },
|
||||
labels: { color: fontColor },
|
||||
};
|
||||
if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
|
||||
legend.display = false;
|
||||
@@ -16438,42 +16436,35 @@
|
||||
else {
|
||||
legend.position = chart.legendPosition;
|
||||
}
|
||||
config.options.legend = { ...config.options?.legend, ...legend };
|
||||
config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
|
||||
config.options.layout = {
|
||||
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||
};
|
||||
config.options.scales = {
|
||||
xAxes: [
|
||||
{
|
||||
ticks: {
|
||||
// x axis configuration
|
||||
maxRotation: 60,
|
||||
minRotation: 15,
|
||||
padding: 5,
|
||||
labelOffset: 2,
|
||||
fontColor,
|
||||
x: {
|
||||
ticks: {
|
||||
padding: 5,
|
||||
color: fontColor,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
position: chart.verticalAxisPosition,
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
color: fontColor,
|
||||
callback: (value) => {
|
||||
return localeFormat.format
|
||||
? formatValue(value, localeFormat)
|
||||
: value?.toLocaleString() || value;
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
position: chart.verticalAxisPosition,
|
||||
ticks: {
|
||||
fontColor,
|
||||
// y axis configuration
|
||||
beginAtZero: true,
|
||||
callback: (value) => {
|
||||
return localeFormat.format
|
||||
? formatValue(value, localeFormat)
|
||||
: value?.toLocaleString() || value;
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
if (chart.stacked) {
|
||||
config.options.scales.xAxes[0].stacked = true;
|
||||
config.options.scales.yAxes[0].stacked = true;
|
||||
// @ts-ignore chart.js type is broken
|
||||
config.options.scales.x.stacked = true;
|
||||
// @ts-ignore chart.js type is broken
|
||||
config.options.scales.y.stacked = true;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@@ -16677,17 +16668,18 @@
|
||||
padding: { left: 30, right: 30, top: chart.title ? 10 : 25, bottom: 25 },
|
||||
};
|
||||
config.options.needle = {
|
||||
radiusPercentage: 2,
|
||||
widthPercentage: 3.2,
|
||||
lengthPercentage: 80,
|
||||
color: "#000000",
|
||||
width: 10,
|
||||
borderColor: "#000000",
|
||||
backgroundColor: "#000000",
|
||||
};
|
||||
config.options.valueLabel = {
|
||||
display: false,
|
||||
formatter: null,
|
||||
color: "#FFFFFF",
|
||||
font: {
|
||||
size: 30,
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
backgroundColor: "#000000",
|
||||
fontSize: 30,
|
||||
borderColor: "#000000",
|
||||
borderRadius: 5,
|
||||
padding: {
|
||||
top: 5,
|
||||
@@ -16695,7 +16687,6 @@
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
},
|
||||
bottomMarginPercentage: 5,
|
||||
};
|
||||
return config;
|
||||
}
|
||||
@@ -16737,16 +16728,12 @@
|
||||
data.push(point.value);
|
||||
backgroundColor.push(point.color);
|
||||
});
|
||||
// There's a bug in gauge lib when the last element in `data` is 0 (i.e. when the range maximum is 0).
|
||||
// The value wrongly fallbacks to 1 because 0 is falsy
|
||||
// See https://github.com/haiiaaa/chartjs-gauge/pull/33
|
||||
// https://github.com/haiiaaa/chartjs-gauge/blob/2ea50541d754d710cb30c2502fa690ac5dc27afd/src/controllers/controller.gauge.js#L52
|
||||
data.push(maxNeedleValue);
|
||||
backgroundColor.push(colors.upperColor);
|
||||
const dataRange = chart.dataRange;
|
||||
const deltaBeyondRangeLimit = needleCoverage / 30;
|
||||
let needleValue = minNeedleValue - deltaBeyondRangeLimit; // make needle value always at the minimum by default
|
||||
let cellFormatter = null;
|
||||
let cellFormatter = undefined;
|
||||
let displayValue = false;
|
||||
if (dataRange !== undefined) {
|
||||
const cell = getters.getEvaluatedCell({
|
||||
@@ -16761,6 +16748,7 @@
|
||||
// smaller and slightly larger than minRange and maxRange to mark the fact
|
||||
// that the needle is out of the range limits
|
||||
needleValue = clip(cell.value, minNeedleValue - deltaBeyondRangeLimit, maxNeedleValue + deltaBeyondRangeLimit);
|
||||
// show the original value, not the clipped one
|
||||
cellFormatter = () => getters.getRangeFormattedValues(dataRange)[0];
|
||||
displayValue = true;
|
||||
}
|
||||
@@ -16808,31 +16796,29 @@
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Regex to test if a format string is a date format that can be translated into a moment time format
|
||||
* Regex to test if a format string is a date format that can be translated into a luxon time format
|
||||
*/
|
||||
const timeFormatMomentCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
|
||||
const timeFormatLuxonCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\s|\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;
|
||||
/** Get the time options for the XAxis of ChartJS */
|
||||
function getChartTimeOptions(labels, labelFormat, locale) {
|
||||
const momentFormat = convertDateFormatForMoment(labelFormat);
|
||||
const timeUnit = getBestTimeUnitForScale(labels, momentFormat, locale);
|
||||
const luxonFormat = convertDateFormatForLuxon(labelFormat);
|
||||
const timeUnit = getBestTimeUnitForScale(labels, luxonFormat, locale);
|
||||
const displayFormats = {};
|
||||
if (timeUnit) {
|
||||
displayFormats[timeUnit] = momentFormat;
|
||||
displayFormats[timeUnit] = luxonFormat;
|
||||
}
|
||||
return {
|
||||
parser: momentFormat,
|
||||
parser: luxonFormat,
|
||||
displayFormats,
|
||||
unit: timeUnit,
|
||||
unit: timeUnit ?? false,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert the given date format into a format that moment.js understands.
|
||||
*
|
||||
* https://momentjs.com/docs/#/parsing/string-format/
|
||||
* https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens
|
||||
*/
|
||||
function convertDateFormatForMoment(format) {
|
||||
format = format.replace(/y/g, "Y");
|
||||
format = format.replace(/d/g, "D");
|
||||
function convertDateFormatForLuxon(format) {
|
||||
// "m" before "h" === month, "m" after "h" === minute
|
||||
const indexH = format.indexOf("h");
|
||||
if (indexH >= 0) {
|
||||
@@ -16903,6 +16889,8 @@
|
||||
return "year";
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const Chart = window.Chart;
|
||||
class LineChart extends AbstractChart {
|
||||
dataSets;
|
||||
labelRange;
|
||||
@@ -17032,7 +17020,7 @@
|
||||
return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);
|
||||
}
|
||||
function getChartAxisType(chart, getters) {
|
||||
if (isDateChart(chart, getters)) {
|
||||
if (isDateChart(chart, getters) && isLuxonTimeAdapterInstalled()) {
|
||||
return "time";
|
||||
}
|
||||
if (isLinearChart(chart, getters)) {
|
||||
@@ -17055,7 +17043,7 @@
|
||||
col: labelRange.zone.left,
|
||||
row: labelRange.zone.top,
|
||||
}).format;
|
||||
return Boolean(labelFormat && timeFormatMomentCompatible.test(labelFormat));
|
||||
return Boolean(labelFormat && timeFormatLuxonCompatible.test(labelFormat));
|
||||
}
|
||||
function canBeLinearChart(labelRange, getters) {
|
||||
if (!labelRange) {
|
||||
@@ -17075,10 +17063,11 @@
|
||||
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
||||
const legend = {
|
||||
labels: {
|
||||
fontColor,
|
||||
color: fontColor,
|
||||
generateLabels(chart) {
|
||||
// color the legend labels with the dataset color, without any transparency
|
||||
const { data } = chart;
|
||||
const labels = window.Chart.defaults.global.legend.labels.generateLabels(chart);
|
||||
const labels = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
||||
for (const [index, label] of labels.entries()) {
|
||||
label.fillStyle = data.datasets[index].borderColor;
|
||||
}
|
||||
@@ -17092,41 +17081,33 @@
|
||||
else {
|
||||
legend.position = chart.legendPosition;
|
||||
}
|
||||
config.options.legend = { ...config.options?.legend, ...legend };
|
||||
Object.assign(config.options.plugins.legend || {}, legend);
|
||||
config.options.layout = {
|
||||
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||
};
|
||||
config.options.scales = {
|
||||
xAxes: [
|
||||
{
|
||||
ticks: {
|
||||
// x axis configuration
|
||||
maxRotation: 60,
|
||||
minRotation: 15,
|
||||
padding: 5,
|
||||
labelOffset: 2,
|
||||
fontColor,
|
||||
x: {
|
||||
ticks: {
|
||||
padding: 5,
|
||||
color: fontColor,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
position: chart.verticalAxisPosition,
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
color: fontColor,
|
||||
callback: (value) => {
|
||||
return localeFormat.format
|
||||
? formatValue(value, localeFormat)
|
||||
: value?.toLocaleString() || value;
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
position: chart.verticalAxisPosition,
|
||||
ticks: {
|
||||
fontColor,
|
||||
// y axis configuration
|
||||
beginAtZero: true,
|
||||
callback: (value) => {
|
||||
return localeFormat.format
|
||||
? formatValue(value, localeFormat)
|
||||
: value?.toLocaleString() || value;
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
if (chart.stacked) {
|
||||
config.options.scales.yAxes[0].stacked = true;
|
||||
if (chart.stacked && config.options?.scales?.y) {
|
||||
// @ts-ignore chart.js type is wrong
|
||||
config.options.scales.y.stacked = true;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@@ -17153,15 +17134,18 @@
|
||||
const config = getLineConfiguration(chart, labels, localeFormat);
|
||||
const labelFormat = getChartLabelFormat(getters, chart.labelRange);
|
||||
if (axisType === "time") {
|
||||
config.options.scales.xAxes[0].type = "time";
|
||||
config.options.scales.xAxes[0].time = getChartTimeOptions(labels, labelFormat, locale);
|
||||
config.options.scales.xAxes[0].ticks.maxTicksLimit = 15;
|
||||
const axis = {
|
||||
type: "time",
|
||||
time: getChartTimeOptions(labels, labelFormat, locale),
|
||||
};
|
||||
Object.assign(config.options.scales.x, axis);
|
||||
config.options.scales.x.ticks.maxTicksLimit = 15;
|
||||
}
|
||||
else if (axisType === "linear") {
|
||||
config.options.scales.xAxes[0].type = "linear";
|
||||
config.options.scales.xAxes[0].ticks.callback = (value) => formatValue(value, localeFormat);
|
||||
config.options.tooltips.callbacks.title = (tooltipItem) => {
|
||||
return formatValue(tooltipItem[0]?.xLabel || "", localeFormat);
|
||||
config.options.scales.x.type = "linear";
|
||||
config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
|
||||
config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
|
||||
return formatValue(tooltipItem[0]?.label || "", localeFormat);
|
||||
};
|
||||
}
|
||||
const colors = new ChartColors();
|
||||
@@ -17179,7 +17163,7 @@
|
||||
const dataset = {
|
||||
label,
|
||||
data,
|
||||
lineTension: 0,
|
||||
tension: 0,
|
||||
borderColor: color,
|
||||
backgroundColor,
|
||||
pointBackgroundColor: color,
|
||||
@@ -17189,6 +17173,22 @@
|
||||
}
|
||||
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
||||
}
|
||||
let missingTimeAdapterAlreadyWarned = false;
|
||||
function isLuxonTimeAdapterInstalled() {
|
||||
// @ts-ignore
|
||||
if (!window.Chart) {
|
||||
return false;
|
||||
}
|
||||
// @ts-ignore
|
||||
const adapter = new window.Chart._adapters._date({});
|
||||
// @ts-ignore
|
||||
const isInstalled = adapter._id === "luxon";
|
||||
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
||||
missingTimeAdapterAlreadyWarned = true;
|
||||
console.warn("'chartjs-adapter-luxon' time adapter is not installed. Time scale axes are disabled.");
|
||||
}
|
||||
return isInstalled;
|
||||
}
|
||||
|
||||
class PieChart extends AbstractChart {
|
||||
dataSets;
|
||||
@@ -17292,7 +17292,7 @@
|
||||
const fontColor = chartFontColor(chart.background);
|
||||
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
||||
const legend = {
|
||||
labels: { fontColor },
|
||||
labels: { color: fontColor },
|
||||
};
|
||||
if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
|
||||
legend.display = false;
|
||||
@@ -17300,12 +17300,12 @@
|
||||
else {
|
||||
legend.position = chart.legendPosition;
|
||||
}
|
||||
config.options.legend = { ...config.options?.legend, ...legend };
|
||||
Object.assign(config.options.plugins.legend || {}, legend);
|
||||
config.options.layout = {
|
||||
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
||||
};
|
||||
config.options.tooltips.callbacks.title = function (tooltipItems, data) {
|
||||
return data.datasets[tooltipItems[0].datasetIndex].label;
|
||||
config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
|
||||
return tooltipItems[0].dataset.label;
|
||||
};
|
||||
return config;
|
||||
}
|
||||
@@ -18069,6 +18069,278 @@
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (window.Chart) {
|
||||
// @ts-ignore
|
||||
const DoughnutController = window.Chart?.DoughnutController;
|
||||
/**
|
||||
* Example :
|
||||
* const chart = new Chart(ctx, {
|
||||
* type: "gauge",
|
||||
* data: {
|
||||
* datasets: [
|
||||
* {
|
||||
* borderWidth: 5,
|
||||
* data: [10, 20, 10],
|
||||
* value: 23,
|
||||
* backgroundColor: [
|
||||
* "#dc3545", //red
|
||||
* "#ffc107", //orange
|
||||
* "#28a745", //green
|
||||
* ],
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* options: {
|
||||
* plugins: {
|
||||
* title: {
|
||||
* display: true,
|
||||
* text: "Custom Chart Title",
|
||||
* padding: {
|
||||
* top: 10,
|
||||
* bottom: 30,
|
||||
* },
|
||||
* font : {
|
||||
* size : 30
|
||||
* }
|
||||
* },
|
||||
* },
|
||||
* valueLabel: {
|
||||
* display: true,
|
||||
* format: (value) => {
|
||||
* return value.toFixed(2) + "%";
|
||||
* },
|
||||
* font : {
|
||||
* size : 35,
|
||||
* family : "Arial",
|
||||
* color : "#FC0",
|
||||
* },
|
||||
* backgroundColor : "#ccc",
|
||||
* borderColor : "#090",
|
||||
* borderRadius : 50
|
||||
* },
|
||||
* needle: {
|
||||
* display: true,
|
||||
* width: 50,
|
||||
* color: "#DC5",
|
||||
* backgroundColor : "#ccc",
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
class GaugeController extends DoughnutController {
|
||||
static id = "gauge";
|
||||
static defaults = {
|
||||
...DoughnutController.defaults,
|
||||
circumference: 180,
|
||||
rotation: 270,
|
||||
};
|
||||
static overrides = {
|
||||
aspectRatio: 2,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
get chartHeight() {
|
||||
return Math.abs(this.chart.chartArea.top - this.chart.chartArea.bottom);
|
||||
}
|
||||
get chartWidth() {
|
||||
return Math.abs(this.chart.chartArea.left - this.chart.chartArea.right);
|
||||
}
|
||||
get needleOptions() {
|
||||
const { config } = this.chart;
|
||||
const options = config.options;
|
||||
// @ts-ignore
|
||||
return options?.needle || {};
|
||||
}
|
||||
get valueLabelOptions() {
|
||||
const { config } = this.chart;
|
||||
const options = config.options;
|
||||
// @ts-ignore
|
||||
return options?.valueLabel || {};
|
||||
}
|
||||
get minValue() {
|
||||
const dataset = this.getDataset();
|
||||
// @ts-ignore
|
||||
return dataset.minValue || 0;
|
||||
}
|
||||
getValueAngleInPercent(value) {
|
||||
const dataset = this.getDataset();
|
||||
const data = dataset.data || [];
|
||||
const max = Math.max(...data);
|
||||
const min = this.minValue;
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
drawValueLabel(params) {
|
||||
const { ctx } = this.chart;
|
||||
if (this.valueLabelOptions.display === false) {
|
||||
return;
|
||||
}
|
||||
// draw background rectangle
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = params.valueLabel.backgroundColor;
|
||||
ctx.strokeStyle = params.valueLabel.borderColor;
|
||||
ctx.roundRect(params.valueLabel.rect.x, params.valueLabel.rect.y, params.valueLabel.rect.width, params.valueLabel.rect.height, params.valueLabel.borderRadius);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
// draw value text
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = params.valueLabel.font;
|
||||
ctx.fillStyle = params.valueLabel.textColor;
|
||||
ctx.fillText(params.valueLabel.valueText, params.valueLabel.textPosition.x, params.valueLabel.textPosition.y);
|
||||
ctx.restore();
|
||||
}
|
||||
drawNeedle(params) {
|
||||
const { ctx } = this.chart;
|
||||
if (this.needleOptions.display === false) {
|
||||
return;
|
||||
}
|
||||
// translate & rotate next paths
|
||||
ctx.save();
|
||||
ctx.translate(params.needle.position.x, params.needle.position.y);
|
||||
ctx.rotate(-Math.PI / 2 + Math.PI * params.needle.percent);
|
||||
// draw circle
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = params.needle.backgroundColor;
|
||||
ctx.strokeStyle = params.needle.borderColor;
|
||||
ctx.ellipse(0, 0, params.needle.width / 2, params.needle.width / 2, 0, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
// draw needle
|
||||
ctx.fillStyle = params.needle.backgroundColor;
|
||||
ctx.strokeStyle = params.needle.borderColor;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-params.needle.width / 2, 0);
|
||||
ctx.lineTo(0, -params.needle.height + 10);
|
||||
ctx.lineTo(+params.needle.width / 2, 0);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
computeRenderingParams() {
|
||||
const { ctx, config } = this.chart;
|
||||
const options = config.options;
|
||||
options.layout = options.layout || {};
|
||||
options.layout.padding = options.layout.padding || {};
|
||||
const formatter = this.valueLabelOptions.formatter;
|
||||
const fmt = typeof formatter === "function" ? formatter : (value) => value;
|
||||
const dataset = this.getDataset();
|
||||
// @ts-ignore value is a custom property for gauge charts
|
||||
const value = dataset.value || 0;
|
||||
const valueText = fmt(value).toString();
|
||||
const percent = this.getValueAngleInPercent(value);
|
||||
const fontFamily = this.valueLabelOptions.font?.family || "Arial";
|
||||
const optionsFontSize = this.valueLabelOptions.font?.size || 0;
|
||||
const fontSize = optionsFontSize >= 1 ? optionsFontSize : 30;
|
||||
const font = `${fontSize}px ${fontFamily}`;
|
||||
const padding = {
|
||||
right: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
...this.valueLabelOptions.padding,
|
||||
};
|
||||
padding.left = padding.left >= 0 ? padding.left : 10;
|
||||
padding.right = padding.right >= 0 ? padding.right : 10;
|
||||
padding.top = padding.top >= 0 ? padding.top : 10;
|
||||
padding.bottom = padding.bottom >= 0 ? padding.bottom : 10;
|
||||
const chartArea = this.chart.chartArea;
|
||||
const offsetX = this.offsetX || 0;
|
||||
const offsetY = this.offsetY || 0;
|
||||
const centerX = (chartArea.left + chartArea.right) / 2;
|
||||
const centerY = (chartArea.top + chartArea.bottom) / 2;
|
||||
const center = {
|
||||
x: centerX + offsetX,
|
||||
y: centerY + offsetY,
|
||||
};
|
||||
const textPosition = {
|
||||
x: center.x,
|
||||
y: center.y,
|
||||
};
|
||||
ctx.save();
|
||||
ctx.font = font;
|
||||
const metrics = ctx.measureText(valueText);
|
||||
ctx.restore();
|
||||
const textHeight = metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent;
|
||||
return {
|
||||
needle: {
|
||||
borderColor: this.needleOptions.borderColor || "#000",
|
||||
backgroundColor: this.needleOptions.backgroundColor || "#000",
|
||||
width: this.needleOptions.width || 10,
|
||||
height: Math.min(this.chartHeight, this.chartWidth / 2),
|
||||
position: {
|
||||
x: textPosition.x,
|
||||
y: textPosition.y,
|
||||
},
|
||||
percent,
|
||||
},
|
||||
valueLabel: {
|
||||
textColor: this.valueLabelOptions.font?.color || "#FFF",
|
||||
backgroundColor: this.valueLabelOptions.backgroundColor || "#000",
|
||||
borderColor: this.valueLabelOptions.borderColor || "#000",
|
||||
borderRadius: this.valueLabelOptions.borderRadius || 10,
|
||||
rect: {
|
||||
x: center.x - metrics.width / 2 - padding.left,
|
||||
y: center.y + textHeight / 2 + padding.top,
|
||||
width: metrics.width + padding.left + padding.right,
|
||||
height: -(textHeight + 6 + padding.top + padding.bottom),
|
||||
},
|
||||
valueText,
|
||||
textPosition,
|
||||
textHeight,
|
||||
font,
|
||||
},
|
||||
};
|
||||
}
|
||||
draw() {
|
||||
super.draw();
|
||||
const params = this.computeRenderingParams();
|
||||
this.drawNeedle(params);
|
||||
this.drawValueLabel(params);
|
||||
}
|
||||
updateElements(elements, start, count, mode) {
|
||||
super.updateElements(elements, start, count, mode);
|
||||
const dataset = this.getDataset();
|
||||
const data = this.getDataset().data;
|
||||
// @ts-ignore
|
||||
const minValue = dataset.minValue;
|
||||
// @ts-ignore
|
||||
const rotation = this.chart.options.rotation || 0;
|
||||
// @ts-ignore
|
||||
const circumference = this.chart.options.circumference || 0;
|
||||
for (let arcIndex = 0; arcIndex < data.length; arcIndex++) {
|
||||
const previousValue = arcIndex === 0 ? minValue : data[arcIndex - 1];
|
||||
const startAngleInPercent = this.getValueAngleInPercent(previousValue);
|
||||
const endAngleInPercent = this.getValueAngleInPercent(data[arcIndex]);
|
||||
const startAngle = degreesToRadians(rotation + circumference * startAngleInPercent) - Math.PI / 2;
|
||||
const endAngle = degreesToRadians(rotation + circumference * endAngleInPercent) - Math.PI / 2;
|
||||
const arcCircumference = endAngle - startAngle;
|
||||
const arc = elements[arcIndex];
|
||||
const propertiesUpdates = {
|
||||
startAngle,
|
||||
endAngle,
|
||||
circumference: arcCircumference,
|
||||
};
|
||||
this.updateElement(arc, arcIndex, propertiesUpdates, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
function degreesToRadians(degrees) {
|
||||
return (degrees * Math.PI) / 180;
|
||||
}
|
||||
// @ts-ignore
|
||||
window.Chart.register(GaugeController);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -36418,7 +36690,8 @@
|
||||
if (!chart) {
|
||||
throw new Error(`No chart for the given id: ${figureId}`);
|
||||
}
|
||||
this.history.update("charts", figureId, this.createRuntimeChart(chart));
|
||||
const runtime = this.createRuntimeChart(chart);
|
||||
this.history.update("charts", figureId, runtime);
|
||||
}
|
||||
return this.charts[figureId];
|
||||
}
|
||||
@@ -49506,7 +49779,14 @@
|
||||
`;
|
||||
}
|
||||
|
||||
const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo name="TableStyleLight8" showFirstColumn="0" showLastColumn="0" showRowStripes="0" showColumnStripes="0"/>`;
|
||||
const TABLE_DEFAULT_ATTRS = [
|
||||
["name", "TableStyleLight8"],
|
||||
["showFirstColumn", "0"],
|
||||
["showLastColumn", "0"],
|
||||
["showRowStripes", "0"],
|
||||
["showColumnStripes", "0"],
|
||||
];
|
||||
const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(TABLE_DEFAULT_ATTRS)}/>`;
|
||||
function createTable(table, tableId, sheetData) {
|
||||
const tableAttributes = [
|
||||
["id", tableId],
|
||||
@@ -50625,9 +50905,9 @@
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
|
||||
__info__.version = '16.5.0-alpha.3';
|
||||
__info__.date = '2023-08-09T12:01:15.162Z';
|
||||
__info__.hash = 'b0e8fd5';
|
||||
__info__.version = '16.5.0-alpha.4';
|
||||
__info__.date = '2023-08-10T09:44:45.710Z';
|
||||
__info__.hash = 'c8db018';
|
||||
|
||||
|
||||
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|
||||
|
||||
@@ -3951,7 +3951,7 @@
|
||||
</t>
|
||||
</odoo>
|
||||
<!--
|
||||
__info__.version = '16.5.0-alpha.3';
|
||||
__info__.date = '2023-08-09T12:01:17.859Z';
|
||||
__info__.hash = 'b0e8fd5';
|
||||
__info__.version = '16.5.0-alpha.4';
|
||||
__info__.date = '2023-08-10T09:44:48.609Z';
|
||||
__info__.hash = 'c8db018';
|
||||
-->
|
||||
|
||||
Reference in New Issue
Block a user