111 lines
2.9 KiB
TypeScript
111 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import 'uplot/dist/uPlot.min.css';
|
|
import { useState, useEffect } from 'react';
|
|
import uPlot from 'uplot';
|
|
|
|
import { CardShell } from './CardShell';
|
|
import {
|
|
makeYScale,
|
|
makeAnnotationHooks,
|
|
makeSignalsSeries,
|
|
makeSignalsAxes,
|
|
CURSOR_NO_DRAG,
|
|
} from './plotHelpers';
|
|
import { buildSeriesData } from './seriesData';
|
|
import { usePlot } from './usePlot';
|
|
|
|
import type { ColorMap } from '@/lib/colors';
|
|
import type { AlertConfig, ChartConfig, SessionBoundary, SignalRow, TimeMode } from '@/lib/types';
|
|
|
|
import { getColorMap } from '@/lib/colors';
|
|
import { useApp } from '@/lib/context';
|
|
import { resolveName } from '@/lib/localization';
|
|
|
|
interface Props {
|
|
config: ChartConfig;
|
|
rows: SignalRow[];
|
|
sessions: SessionBoundary[];
|
|
alerts: AlertConfig[];
|
|
timeMode: TimeMode;
|
|
onEdit: () => void;
|
|
onDelete: () => void;
|
|
}
|
|
|
|
export default function SignalsChart({
|
|
config,
|
|
rows,
|
|
sessions,
|
|
alerts,
|
|
timeMode,
|
|
onEdit,
|
|
onDelete,
|
|
}: Props) {
|
|
const { localeMap } = useApp();
|
|
const locale = typeof navigator !== 'undefined' ? navigator.language : 'de-DE';
|
|
const [colorMap, setColorMap] = useState<ColorMap>(new Map());
|
|
useEffect(() => {
|
|
getColorMap().then(setColorMap);
|
|
}, []);
|
|
|
|
const { containerRef, legendRef } = usePlot(
|
|
(el, w, h, lRef) => {
|
|
const data = buildSeriesData(rows, config.signal_type, timeMode);
|
|
if (!data) return null;
|
|
|
|
const { keys, allXs, data: seriesData } = data;
|
|
const sessionXs = sessions.map((s) =>
|
|
timeMode === 'tick' ? s.game_tick : new Date(s.real_time).getTime() / 1000,
|
|
);
|
|
const alertThresholds = alerts
|
|
.filter((a) => config.signal_type === 'both' || config.signal_type === a.signal_type)
|
|
.map((a) => a.threshold);
|
|
|
|
return new uPlot(
|
|
{
|
|
width: w,
|
|
height: h,
|
|
cursor: CURSOR_NO_DRAG,
|
|
legend: {
|
|
mount: (_u, legendEl) => {
|
|
if (lRef.current) lRef.current.appendChild(legendEl);
|
|
},
|
|
},
|
|
series: makeSignalsSeries(keys, timeMode, (key) => resolveName(key, localeMap), colorMap),
|
|
axes: makeSignalsAxes(timeMode, locale),
|
|
scales: {
|
|
x: { time: false },
|
|
y: makeYScale(config.y_min ?? null, config.y_max ?? null, config.y_scale),
|
|
},
|
|
hooks: makeAnnotationHooks(sessionXs, alertThresholds),
|
|
},
|
|
[allXs, ...seriesData],
|
|
el,
|
|
);
|
|
},
|
|
[
|
|
rows,
|
|
sessions,
|
|
alerts,
|
|
config.signal_type,
|
|
config.y_min,
|
|
config.y_max,
|
|
config.y_scale,
|
|
timeMode,
|
|
localeMap,
|
|
],
|
|
);
|
|
|
|
return (
|
|
<CardShell
|
|
title={config.title}
|
|
onEdit={onEdit}
|
|
onDelete={onDelete}
|
|
empty={rows.length === 0}
|
|
legendContainerRef={legendRef}
|
|
>
|
|
<div ref={containerRef as React.RefObject<HTMLDivElement>} className="w-full h-full" />
|
|
</CardShell>
|
|
);
|
|
}
|