import { t } from '@/i18n'; import { cn } from '@/lib/cn'; import { isNum } from '@/lib/format'; import type { DNAResponse, DnaDimension } from '@/lib/types'; import { CHART } from './palette'; export const DNA_DIMS: DnaDimension[] = ['income', 'demographics', 'urbanization', 'trade', 'energy', 'emissions', 'innovation', 'education', 'public_spending']; /** * Radial "fingerprint": 9 dimensions, 0โ€“100 percentile rank, drawn as a filled polygon on a 100-radius ring * with 25/50/75 guide rings and labelled spokes. Optional dashed `reference` polygon (world = 50, a region's * median, another country) for comparison. Server-renderable SVG in a square viewBox. */ export function DnaRadial({ dna, name, size = 320, className, compact = false, reference, referenceLabel }: { dna: Pick | null; name: string; size?: number; className?: string; compact?: boolean; reference?: Record | null; referenceLabel?: string | null }) { const dims = DNA_DIMS.map((d) => ({ key: d, label: t(`country.dna.${d}` as const), value: dna?.dims?.[d] ?? null, ref: reference?.[d] ?? null })); const present = dims.filter((d) => isNum(d.value)); if (!dna || present.length === 0) return

{t('country.dna.none')}

; const R = 100; const pad = compact ? 16 : 100; const cx = R + pad; const cy = R + pad; const W = 2 * (R + pad); const angle = (i: number) => -Math.PI / 2 + (i / dims.length) * 2 * Math.PI; const pt = (i: number, r: number): [number, number] => [cx + r * Math.cos(angle(i)), cy + r * Math.sin(angle(i))]; const poly = dims.map((d, i) => pt(i, isNum(d.value) ? (d.value / 100) * R : 0)); const refPoly = reference ? dims.map((d, i) => pt(i, isNum(d.ref) ? (d.ref / 100) * R : 0)) : null; const summary = t('chart.summary.dna', { name, dims: present.map((d) => `${d.label} ${Math.round(d.value as number)}`).join(', ') }); return (
{t('country.dna.title')} {summary} {[25, 50, 75, 100].map((r) => ( ))} {dims.map((_, i) => { const [x, y] = pt(i, R); return ; })} {refPoly ? p.join(',')).join(' ')} fill={CHART.ink3} fillOpacity={0.08} stroke={CHART.ink2} strokeWidth={1.5} strokeDasharray="4 3" strokeLinejoin="round" /> : null} p.join(',')).join(' ')} fill={CHART.accent} fillOpacity={0.16} stroke={CHART.accent} strokeWidth={2} strokeLinejoin="round" /> {poly.map(([x, y], i) => ( ))} {!compact ? dims.map((d, i) => { const [x, y] = pt(i, R + 16); const a = angle(i); const anchor = Math.abs(Math.cos(a)) < 0.2 ? 'middle' : Math.cos(a) > 0 ? 'start' : 'end'; return ( {d.label} {isNum(d.value) ? ` ${Math.round(d.value)}` : ' โ€”'} {reference && isNum(d.ref) ? {` ยท ${Math.round(d.ref)}`} : null} ); }) : null}
{compact ? ( dims.map((d) => ( {d.label} {isNum(d.value) ? Math.round(d.value) : 'โ€”'} )) ) : ( <> {name} {reference && referenceLabel ? ( {referenceLabel} ) : null} )}
); }