spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import { t } from '@/i18n';2import { formatValue } from '@/lib/format';3import { classIndex, isoLookupFromCountries, worldPaths } from '@/lib/map-geo';4import type { CountrySummary, MapResponse } from '@/lib/types';5import { ChoroplethView, type ChoroplethFeature } from './choropleth-view';67/**8 * Server component: joins the world geometry with `/indicators/{slug}/map` values and the country list9 * (for slugs and the ISO-numeric lookup), computes 5–7 quantile classes from the API legend, and hands10 * plain data to the interactive client view. Sequential single-hue ramp (light → dark = low → high).11 */12export function Choropleth({13 map,14 countries,15 height,16 compact = false,17 className,18 title,19}: {20 map: MapResponse;21 countries: CountrySummary[];22 height?: number;23 compact?: boolean;24 className?: string;25 title?: string;26}) {27 const { paths, sphere } = worldPaths();28 const byId = new Map(countries.map((c) => [c.id, c]));29 // Registry numeric → ISO3 lookup (when the API exposes iso_numeric on the summary); static table otherwise.30 const numeric = isoLookupFromCountries(countries as Array<{ id: string; iso_numeric?: string | null }>);31 const breaks = (map.legend?.breaks ?? []).slice(0, 6);32 const k = breaks.length + 1; // classes33 const features: ChoroplethFeature[] = paths.map((p) => {34 // Prefer registry numeric lookup when the static table has no code.35 const iso3 = p.iso3 ?? (p.numeric ? numeric.get(p.numeric) ?? null : null);36 const c = iso3 ? byId.get(iso3) : undefined;37 const v = iso3 ? map.values[iso3] : undefined;38 return {39 iso3,40 name: c?.name ?? p.name,41 slug: c?.slug ?? null,42 flag: c?.flag ?? null,43 d: p.d,44 value: typeof v === 'number' ? v : null,45 cls: typeof v === 'number' ? classIndex(v, breaks) : null,46 };47 });48 const spec = map.indicator;49 const legendItems = Array.from({ length: k }, (_, i) => {50 const lo = i === 0 ? map.legend.min : breaks[i - 1]!;51 const hi = i === k - 1 ? map.legend.max : breaks[i]!;52 return { cls: i, label: `${formatValue(lo, spec)} – ${formatValue(hi, spec)}` };53 });54 const year = map.year_used ?? map.year ?? '';55 const summary = t('chart.summary.map', { name: spec.name, year, n: map.n, min: formatValue(map.legend.min, spec), max: formatValue(map.legend.max, spec) });56 return (57 <ChoroplethView58 features={features}59 sphere={sphere}60 legend={legendItems}61 k={k}62 spec={{ format: spec.format, unit: spec.unit, unit_short: spec.unit_short, precision: spec.precision, name: spec.name }}63 summary={summary}64 title={title ?? t('chart.map.legend', { name: spec.name, year })}65 height={height}66 compact={compact}67 className={className}68 />69 );70}71