Skip to content

Commit 74b36d1

Browse files
committed
[FIX] Charts: Ensure Chart js extension are loaded on chart creation
When calling the method chartToImage, the chartJs extensions might not be loaded as we load them conditionally when mounting a chart. Hence, calling `ChartToImage` when there are no visible charts in the viewport or if we just instantiate a model without loading a component `Spreadsheet`, the extensions will be missing. Right now, the plugins/extensions are not fundamental to convert a chart to an image but this becomes problematic with the arrival of future charts (for instance Funnel). This commit adds a check to ensure that the extensisons are loaded and unloads them after use as they can cause crashes when using the global `ChartJs` variable elsewhere (see #6076). Task: 5214007 X-original-commit: d146422
1 parent b7280f7 commit 74b36d1

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<<<<<<< HEAD
2+
||||||| MERGE BASE
3+
=======
4+
import type { ChartConfiguration, ChartOptions } from "chart.js";
5+
import {
6+
areChartJSExtensionsLoaded,
7+
registerChartJSExtensions,
8+
unregisterChartJsExtensions,
9+
} from "../../../components/figures/chart/chartJs/chart_js_extension";
10+
import { Figure } from "../../../types";
11+
import { ChartType, GaugeChartRuntime, ScorecardChartRuntime } from "../../../types/chart";
12+
import { ChartRuntime } from "../../../types/chart/chart";
13+
import { deepCopy } from "../../misc";
14+
import { drawGaugeChart } from "./gauge_chart_rendering";
15+
import { drawScoreChart } from "./scorecard_chart";
16+
import { getScorecardConfiguration } from "./scorecard_chart_config_builder";
17+
18+
export const CHART_COMMON_OPTIONS: ChartOptions = {
19+
// https://www.chartjs.org/docs/latest/general/responsive.html
20+
responsive: true, // will resize when its container is resized
21+
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
22+
elements: {
23+
line: {
24+
fill: false, // do not fill the area under line charts
25+
},
26+
point: {
27+
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
28+
},
29+
},
30+
animation: false,
31+
events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
32+
};
33+
34+
export function chartToImageUrl(
35+
runtime: ChartRuntime,
36+
figure: Figure,
37+
type: ChartType
38+
): string | undefined {
39+
// wrap the canvas in a div with a fixed size because chart.js would
40+
// fill the whole page otherwise
41+
const div = document.createElement("div");
42+
div.style.width = `${figure.width}px`;
43+
div.style.height = `${figure.height}px`;
44+
const canvas = document.createElement("canvas");
45+
div.append(canvas);
46+
canvas.setAttribute("width", figure.width.toString());
47+
canvas.setAttribute("height", figure.height.toString());
48+
let imageContent: string | undefined;
49+
// we have to add the canvas to the DOM otherwise it won't be rendered
50+
document.body.append(div);
51+
if ("chartJsConfig" in runtime) {
52+
const extensionsLoaded = areChartJSExtensionsLoaded();
53+
if (!extensionsLoaded) {
54+
registerChartJSExtensions();
55+
}
56+
const config = deepCopy(runtime.chartJsConfig);
57+
config.plugins = [backgroundColorChartJSPlugin];
58+
const chart = new window.Chart(canvas, config as ChartConfiguration);
59+
imageContent = chart.toBase64Image() as string;
60+
chart.destroy();
61+
if (!extensionsLoaded) {
62+
unregisterChartJsExtensions();
63+
}
64+
} else if (type === "scorecard") {
65+
const design = getScorecardConfiguration(figure, runtime as ScorecardChartRuntime);
66+
drawScoreChart(design, canvas);
67+
imageContent = canvas.toDataURL();
68+
} else if (type === "gauge") {
69+
drawGaugeChart(canvas, runtime as GaugeChartRuntime);
70+
imageContent = canvas.toDataURL();
71+
}
72+
div.remove();
73+
return imageContent;
74+
}
75+
76+
export async function chartToImageFile(
77+
runtime: ChartRuntime,
78+
figure: Figure,
79+
type: ChartType
80+
): Promise<File | undefined> {
81+
// wrap the canvas in a div with a fixed size because chart.js would
82+
// fill the whole page otherwise
83+
const div = document.createElement("div");
84+
div.style.width = `${figure.width}px`;
85+
div.style.height = `${figure.height}px`;
86+
div.style.position = "fixed";
87+
div.style.opacity = "0";
88+
const canvas = document.createElement("canvas");
89+
div.append(canvas);
90+
canvas.setAttribute("width", figure.width.toString());
91+
canvas.setAttribute("height", figure.height.toString());
92+
// we have to add the canvas to the DOM otherwise it won't be rendered
93+
document.body.append(div);
94+
let chartBlob: Blob | null = null;
95+
if ("chartJsConfig" in runtime) {
96+
const extensionsLoaded = areChartJSExtensionsLoaded();
97+
if (!extensionsLoaded) {
98+
registerChartJSExtensions();
99+
}
100+
const config = deepCopy(runtime.chartJsConfig);
101+
config.plugins = [backgroundColorChartJSPlugin];
102+
const chart = new window.Chart(canvas, config as ChartConfiguration);
103+
chartBlob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, "image/png"));
104+
chart.destroy();
105+
if (!extensionsLoaded) {
106+
unregisterChartJsExtensions();
107+
}
108+
} else if (type === "scorecard") {
109+
const design = getScorecardConfiguration(figure, runtime as ScorecardChartRuntime);
110+
drawScoreChart(design, canvas);
111+
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
112+
} else if (type === "gauge") {
113+
drawGaugeChart(canvas, runtime as GaugeChartRuntime);
114+
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
115+
}
116+
div.remove();
117+
return chartBlob ? new File([chartBlob], "chart.png", { type: "image/png" }) : undefined;
118+
}
119+
120+
/**
121+
* Custom chart.js plugin to set the background color of the canvas
122+
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
123+
*/
124+
const backgroundColorChartJSPlugin = {
125+
id: "customCanvasBackgroundColor",
126+
beforeDraw: (chart) => {
127+
const { ctx } = chart;
128+
ctx.save();
129+
ctx.globalCompositeOperation = "destination-over";
130+
ctx.fillStyle = "#ffffff";
131+
ctx.fillRect(0, 0, chart.width, chart.height);
132+
ctx.restore();
133+
},
134+
};
135+
136+
>>>>>>> FORWARD PORTED

0 commit comments

Comments
 (0)