dimensionlab-website/packages/ui/src/components/telemetry/LineChart.tsx

114 lines
2.9 KiB
TypeScript

import "uplot/dist/uPlot.min.css";
import { useEffect, useRef } from "react";
import type { UiSeverity } from "../../types";
export interface LineChartProps {
values?: number[];
severity?: UiSeverity;
label?: string;
}
export function LineChart({
values = [],
severity = "neutral",
label = "Telemetry trend",
}: LineChartProps) {
const chartElementRef = useRef<HTMLDivElement>(null);
const valuesKey = values.join(",");
useEffect(() => {
const chartElement = chartElementRef.current;
if (!chartElement) return;
let chart: ChartInstance | null = null;
let resizeObserver: ResizeObserver | null = null;
let disposed = false;
void import("uplot").then((module) => {
if (disposed || !chartElementRef.current) return;
const element = chartElementRef.current;
chart = new module.default(chartOptions(element, severity), chartData(values), element);
if (typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(() => {
chart?.setSize(chartSize(element));
});
resizeObserver.observe(element);
}
});
return () => {
disposed = true;
resizeObserver?.disconnect();
chart?.destroy();
};
}, [severity, valuesKey, values]);
return (
<div
className="line-chart"
data-chart-library="uplot"
data-severity={severity}
data-values={values.join(",")}
role="img"
aria-label={label}
>
<div ref={chartElementRef} className="line-chart__canvas" aria-hidden="true" />
</div>
);
}
type ChartInstance = {
destroy(): void;
setSize(size: { width: number; height: number }): void;
};
function chartData(values: number[]): import("uplot").AlignedData {
const normalized = values.length ? values : [0, 0];
return [
normalized.map((_, index) => index),
normalized.map((value) => Math.max(0, Number(value) || 0)),
];
}
function chartOptions(
element: HTMLElement,
severity: UiSeverity,
): import("uplot").Options {
return {
...chartSize(element),
cursor: { show: false },
legend: { show: false },
padding: [2, 0, 2, 0],
scales: {
x: { time: false },
y: { auto: true },
},
axes: [{ show: false }, { show: false }],
series: [
{},
{
stroke: chartStroke(element, severity),
width: 2,
points: { show: false },
},
],
};
}
function chartSize(element?: HTMLElement): { width: number; height: number } {
return {
width: Math.max(80, Math.round(element?.clientWidth || 120)),
height: Math.max(20, Math.round(element?.clientHeight || 24)),
};
}
function chartStroke(element: HTMLElement, severity: UiSeverity): string {
const styles = window.getComputedStyle(element.closest(".line-chart") || element);
const currentColor = styles.color;
if (currentColor) return currentColor;
if (severity === "danger") return "#ff1744";
if (severity === "warning") return "#ffb020";
return "#d7ff00";
}