|
| 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