import { scaleLinear } from 'd3-scale'; import { curveMonotoneX, line as d3line } from 'd3-shape'; import { fmtDate, fmtInt, num } from '@/lib/format'; import type { SatelliteHistory } from '@/lib/types'; /** * Perigee / apogee over time (dual-line, SVG, server-safe). Local variant of the shared AreaChart which is single-series. * Renders only with ≥ 2 daily points — the caller shows an honest note otherwise. */ export function AltitudeChart({ series, height = 180 }: { series: SatelliteHistory['altitude_series']; height?: number }) { const rows = series .map((r) => ({ day: r.day, perigee: num(r.perigee_km), apogee: num(r.apogee_km) })) .filter((r): r is { day: string; perigee: number; apogee: number } => r.perigee !== null && r.apogee !== null); if (rows.length < 2) return null; const W = 640; const H = height; const pad = { l: 44, r: 10, t: 10, b: 24 }; const x = scaleLinear().domain([0, rows.length - 1]).range([pad.l, W - pad.r]); const lo = Math.min(...rows.map((r) => r.perigee)); const hi = Math.max(...rows.map((r) => r.apogee)); const padY = (hi - lo) * 0.15 || 2; const y = scaleLinear().domain([lo - padY, hi + padY]).nice().range([H - pad.b, pad.t]); const mk = (key: 'perigee' | 'apogee') => d3line<(typeof rows)[number]>().x((_, i) => x(i)).y((d) => y(d[key])).curve(curveMonotoneX)(rows) ?? ''; const every = Math.max(1, Math.ceil(rows.length / 6)); return (
Perigee and apogee altitude over time {y.ticks(4).map((t) => ( {fmtInt(t)} ))} {rows.map((r, i) => (i % every === 0 || i === rows.length - 1) && ( {r.day.slice(0, 10)} ))}
); }