/** * Live Chart Component * * Displays real-time data as a simple line chart. */ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { useSocket } from '../../lib/realtime/useSocket'; import type { TransparencyEventType } from '../../lib/realtime/types'; interface LiveChartProps { eventTypes?: TransparencyEventType[]; dataKey?: string; title?: string; color?: string; height?: number; maxDataPoints?: number; showGrid?: boolean; className?: string; } /** * Simple SVG line chart for real-time data */ export function LiveChart({ eventTypes = ['system.metric'], dataKey = 'value', title = 'Live Data', color = '#3B82F6', height = 120, maxDataPoints = 30, showGrid = true, className, }: LiveChartProps) { const { events } = useSocket({ eventTypes, maxEvents: maxDataPoints, }); // Extract data points const dataPoints = useMemo(() => { return events .filter((e) => e.data && typeof e.data[dataKey] === 'number') .map((e) => ({ value: e.data[dataKey] as number, timestamp: new Date(e.timestamp).getTime(), })) .reverse() .slice(-maxDataPoints); }, [events, dataKey, maxDataPoints]); // Calculate chart dimensions const chartWidth = 400; const chartHeight = height - 40; const padding = { top: 10, right: 10, bottom: 20, left: 40 }; const innerWidth = chartWidth - padding.left - padding.right; const innerHeight = chartHeight - padding.top - padding.bottom; // Calculate scales const { minValue, maxValue, points, pathD } = useMemo(() => { if (dataPoints.length === 0) { return { minValue: 0, maxValue: 100, points: [], pathD: '' }; } const values = dataPoints.map((d) => d.value); const min = Math.min(...values); const max = Math.max(...values); const range = max - min || 1; const pts = dataPoints.map((d, i) => ({ x: padding.left + (i / Math.max(1, dataPoints.length - 1)) * innerWidth, y: padding.top + innerHeight - ((d.value - min) / range) * innerHeight, })); const d = pts.length > 0 ? `M ${pts.map((p) => `${p.x},${p.y}`).join(' L ')}` : ''; return { minValue: min, maxValue: max, points: pts, pathD: d }; }, [dataPoints, innerWidth, innerHeight, padding]); // Latest value const latestValue = dataPoints.length > 0 ? dataPoints[dataPoints.length - 1].value : null; return (