92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import { createEffect, onCleanup, onMount } from "solid-js";
|
|
import { Chart, registerables } from "chart.js";
|
|
|
|
// chart.js v4 is tree-shakeable and ships nothing registered by default; register
|
|
// all controllers/elements/scales once so any chart type works (the old UMD shim
|
|
// did this implicitly).
|
|
Chart.register(...registerables);
|
|
|
|
type ChartType = "line" | "bar" | "radar" | "doughnut" | "polarArea" | "bubble" | "pie" | "scatter";
|
|
|
|
interface ReactiveChartProps {
|
|
type: ChartType;
|
|
data: unknown;
|
|
options?: object;
|
|
class?: string;
|
|
}
|
|
|
|
interface ChartInstance {
|
|
destroy(): void;
|
|
update(): void;
|
|
data: unknown;
|
|
options: object;
|
|
}
|
|
|
|
type ChartCtor = new (ctx: CanvasRenderingContext2D, cfg: object) => ChartInstance;
|
|
|
|
export default function ReactiveChart(props: ReactiveChartProps) {
|
|
let canvasRef: HTMLCanvasElement | undefined;
|
|
let chartInstance: ChartInstance | null = null;
|
|
|
|
onMount(() => {
|
|
// Defer initialization until the canvas is connected to the document.
|
|
// @solidjs/router creates route components before inserting them into
|
|
// the DOM, and Chart.js needs getComputedStyle which requires a
|
|
// connected element with ownerDocument.defaultView.
|
|
const init = () => {
|
|
if (!canvasRef) return;
|
|
if (!canvasRef.isConnected) {
|
|
requestAnimationFrame(init);
|
|
return;
|
|
}
|
|
|
|
const ctx = canvasRef.getContext("2d");
|
|
if (!ctx) return;
|
|
|
|
chartInstance = new (Chart as unknown as ChartCtor)(ctx, {
|
|
type: props.type,
|
|
data: props.data,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
...(props.options ?? {}),
|
|
},
|
|
});
|
|
};
|
|
init();
|
|
});
|
|
|
|
onCleanup(() => {
|
|
if (chartInstance) {
|
|
chartInstance.destroy();
|
|
chartInstance = null;
|
|
}
|
|
});
|
|
|
|
createEffect(() => {
|
|
const data = props.data;
|
|
if (chartInstance) {
|
|
chartInstance.data = data;
|
|
chartInstance.update();
|
|
}
|
|
});
|
|
|
|
createEffect(() => {
|
|
const options = props.options;
|
|
if (chartInstance) {
|
|
chartInstance.options = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
...(options ?? {}),
|
|
};
|
|
chartInstance.update();
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div class={"h-full " + (props.class || "")}>
|
|
<canvas ref={(el: HTMLCanvasElement) => canvasRef = el}></canvas>
|
|
</div>
|
|
);
|
|
}
|