/** Shared building blocks for the Closed-Loop Lab page. */ import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; import type { DataLabel } from '../../types/nextgen'; export const PANEL_SX = { p: 1.5, bgcolor: '#161c22', border: '1px solid #232a31', borderRadius: 1.5, } as const; const LABEL_COLORS: Record = { REAL: '#2e7d32', REPLAYED: '#0277bd', SIMULATED: '#6a1b9a', GENERATED: '#ef6c00', COUNTERFACTUAL: '#c62828', }; export function DataLabelChip({ label }: { label: DataLabel | string }) { const color = LABEL_COLORS[label as DataLabel] ?? '#455a64'; return ( ); } export function ScoreChip({ name, value }: { name: string; value: number }) { const color = value >= 0.8 ? '#2e7d32' : value >= 0.5 ? '#e65100' : '#b71c1c'; return ( ); } export function MetricCard({ label, value, unit, tone, }: { label: string; value: string | number | null | undefined; unit?: string; tone?: 'good' | 'bad' | 'neutral'; }) { const color = tone === 'good' ? '#81c784' : tone === 'bad' ? '#ef9a9a' : '#e0e3e7'; return ( {label} {value === null || value === undefined ? '—' : value} {unit && value !== null && value !== undefined ? ( {unit} ) : null} ); } export interface Series { name: string; color: string; points: { x: number; y: number }[]; } /** Dependency-free SVG line chart (time series comparison). */ export function LineChart({ series, height = 160, xLabel, yLabel, }: { series: Series[]; height?: number; xLabel?: string; yLabel?: string; }) { const all = series.flatMap((s) => s.points); if (!all.length) return null; const w = 560; const pad = { l: 42, r: 10, t: 10, b: 24 }; const xs = all.map((p) => p.x); const ys = all.map((p) => p.y); const xMin = Math.min(...xs); const xMax = Math.max(...xs) || 1; const yMin = Math.min(0, ...ys); const yMax = Math.max(...ys) * 1.08 || 1; const sx = (x: number) => pad.l + ((x - xMin) / (xMax - xMin || 1)) * (w - pad.l - pad.r); const sy = (y: number) => height - pad.b - ((y - yMin) / (yMax - yMin || 1)) * (height - pad.t - pad.b); const yTicks = [yMin, (yMin + yMax) / 2, yMax]; const xTicks = [xMin, (xMin + xMax) / 2, xMax]; return ( {yTicks.map((t, i) => ( {t.toFixed(1)} ))} {xTicks.map((t, i) => ( {t.toFixed(1)} ))} {series.map((s) => ( `${sx(p.x)},${sy(p.y)}`).join(' ')} /> ))} {yLabel ? ( {yLabel} ) : null} {xLabel ? ( {xLabel} ) : null} {series.map((s) => ( {s.name} ))} ); }