/** Visualisation panels for /constellation/[slug]. Server components, pure SVG charts. */ import Link from 'next/link'; import { AreaChart, Bars, Donut, Histogram } from '@/components/charts/charts'; import { Unavailable } from '@/components/ui/unavailable'; import { fmtDeg, fmtInt, fmtKm, num, titleCase } from '@/lib/format'; import { STATUS_COLORS, routes } from '@/lib/site'; import type { ConstellationDetail } from '@/lib/types'; import { Derived, ScrollTable } from './shared'; export function DeploymentTimeline({ growth }: { growth: ConstellationDetail['growth'] }) { const monthly = growth.map((g) => ({ x: g.month.slice(0, 7), y: num(g.launched) ?? 0 })); const cumulative = growth.map((g) => ({ x: g.month.slice(0, 7), y: num(g.cumulative) ?? 0 })); if (!growth.length) return ; return (

Cumulative satellites launched

String(x).slice(0, 4)} />

Launched per month

); } export function StatusDonut({ dist, total }: { dist: ConstellationDetail['status_distribution']; total: number | null }) { const data = dist.map((d) => ({ label: titleCase(d.status.toLowerCase()), value: num(d.count) ?? 0, color: STATUS_COLORS[d.status] ?? 'var(--other)' })).filter((d) => d.value > 0).sort((a, b) => b.value - a.value); return ; } export function OrbitalShells({ d }: { d: ConstellationDetail }) { const alt = d.altitude_histogram.map((h) => ({ bin: num(h.alt_km) ?? 0, value: num(h.satellites) ?? 0 })).filter((h) => h.bin >= 0); const incl = d.inclination_histogram.map((h) => ({ bin: num(h.incl_deg) ?? 0, value: num(h.satellites) ?? 0 })); const shells = [...d.shells].sort((a, b) => (num(b.satellites) ?? 0) - (num(a.satellites) ?? 0)).slice(0, 10); const nothing = !alt.length && !incl.length && !shells.length; if (nothing) return ; return (

Perigee altitude · satellites per 25 km

Inclination · satellites per 5°

Largest shells (perigee × inclination)

{shells.length === 0 ? ( ) : ( {shells.map((s, i) => ( ))}
Perigee Inclination Satellites
{fmtKm(s.perigee_km)} {fmtDeg(s.inclination_deg)} {fmtInt(s.satellites)}
)}

Shells bucket current elements to 20 km × 1°. Orbit classes are derived.

); } export function DecaysByMonth({ rows }: { rows: ConstellationDetail['decays_by_month'] }) { const data = rows.map((r) => ({ x: r.month.slice(0, 7), y: num(r.decayed) ?? 0 })); const total = data.reduce((s, d) => s + d.y, 0); if (!data.length) return null; return (

{fmtInt(total)} decays recorded · per month

); } export function MembershipNote({ d }: { d: ConstellationDetail }) { const methods = d.membership_methods ?? []; return (

Constellation membership is from curated rules, never from a single upstream field. Rules for this constellation:

Name patterns
{d.match_patterns.length ? d.match_patterns.map((p) => {p}) : none}
CelesTrak groups
{d.celestrak_groups.length ? d.celestrak_groups.map((g) => {g}) : none}

Members by method

{methods.length ? (
    {methods.map((m) => (
  • {m.method} · {fmtInt(m.satellites)}
  • ))}
) : (

Per-method breakdown unavailable for this constellation.

)}

Full rule set and metric versions on the methodology page. Activity score is a derived index of recent launch cadence, not a safety metric.

); }