spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1'use client';2import Link from 'next/link';3import { t } from '@/i18n';4import { formatValue, grouped, ordinal } from '@/lib/format';5import { routes } from '@/lib/site';6import type { FormatSpec } from '@/lib/types';7import type { DistributionResponse } from '@/lib/types-analytics';8import { Histogram, type HistogramMarker } from '@/components/charts/histogram';910/**11 * Distribution of an indicator across countries: histogram with markers (world median, the highlighted12 * country, its region median) and a table of medians by region and income group.13 */14export function DistributionPanel({ data, spec }: { data: DistributionResponse; spec: FormatSpec }) {15 const markers: HistogramMarker[] = [];16 if (data.stats.median != null) markers.push({ id: 'world', label: t('indicator.dist.worldMedian'), value: data.stats.median, tone: 'ink' });17 const h = data.highlight;18 if (h && h.value != null) markers.push({ id: 'hl', label: h.country.name ?? h.country.id, value: h.value, tone: 'accent' });19 if (h && h.region_median != null && h.region) markers.push({ id: 'region', label: t('indicator.dist.regionMedian', { region: h.region.name ?? '' }), value: h.region_median, tone: 'muted' });20 return (21 <div className="grid gap-x-10 gap-y-6 lg:grid-cols-[minmax(0,3fr)_minmax(0,2fr)]">22 <div className="min-w-0">23 <Histogram edges={data.histogram.edges} counts={data.histogram.counts} log={data.histogram.log} markers={markers} spec={spec} height={240} defaultWidth={720} unitLabel={spec.unit ?? undefined} title={t('indicator.dist.title', { year: data.year_used ?? '' })} subtitle={t('indicator.dist.sub', { n: grouped(data.n), p10: formatValue(data.stats.p10, spec), p90: formatValue(data.stats.p90, spec) })} />24 {h && h.value != null ? (25 <p className="mt-2 text-sm text-ink-2">26 <Link href={routes.country(h.country.slug ?? h.country.id)} className="link-quiet font-medium text-ink">27 <span aria-hidden>{h.country.flag} </span>28 {h.country.name}29 </Link>{' '}30 {t('indicator.dist.highlight', { value: formatValue(h.value, spec), pct: h.percentile != null ? ordinal(Math.round(h.percentile)) : t('common.na'), rank: h.rank ?? '', n: h.n ?? '' })}31 </p>32 ) : null}33 </div>34 <div className="min-w-0">35 <table className="w-full border-collapse text-sm">36 <caption className="sr-only">{t('indicator.dist.medians')}</caption>37 <thead>38 <tr className="border-b border-rule text-left text-2xs uppercase tracking-wide text-ink-3">39 <th scope="col" className="py-1.5 pr-3 font-medium">40 {t('indicator.dist.group')}41 </th>42 <th scope="col" className="py-1.5 pr-3 text-right font-medium">43 {t('indicator.trend.median')}44 </th>45 <th scope="col" className="py-1.5 text-right font-medium">46 {t('indicator.dist.n')}47 </th>48 </tr>49 </thead>50 <tbody className="divide-y divide-rule">51 <tr className="font-medium">52 <td className="py-1.5 pr-3 text-ink">{t('common.world')}</td>53 <td className="tnum py-1.5 pr-3 text-right text-ink">{formatValue(data.stats.median, spec)}</td>54 <td className="tnum py-1.5 text-right text-ink-2">{grouped(data.n)}</td>55 </tr>56 {data.by_region.map((r) => (57 <tr key={r.group.id}>58 <td className="py-1.5 pr-3 text-ink-2">59 <Link href={routes.region(r.group.slug ?? r.group.id)} className="link-quiet">60 {r.group.name}61 </Link>62 </td>63 <td className="tnum py-1.5 pr-3 text-right text-ink">{formatValue(r.median, spec)}</td>64 <td className="tnum py-1.5 text-right text-ink-3">{grouped(r.n)}</td>65 </tr>66 ))}67 {data.by_income.map((r) => (68 <tr key={r.group.id}>69 <td className="py-1.5 pr-3 text-ink-2">70 <Link href={routes.region(r.group.slug ?? r.group.id)} className="link-quiet">71 {r.group.name}72 </Link>73 </td>74 <td className="tnum py-1.5 pr-3 text-right text-ink">{formatValue(r.median, spec)}</td>75 <td className="tnum py-1.5 text-right text-ink-3">{grouped(r.n)}</td>76 </tr>77 ))}78 </tbody>79 </table>80 <dl className="tnum mt-3 grid grid-cols-4 gap-x-3 text-xs">81 {(82 [83 ['p10', data.stats.p10],84 ['p25', data.stats.p25],85 ['p75', data.stats.p75],86 ['p90', data.stats.p90],87 ] as Array<[string, number | null]>88 ).map(([k, v]) => (89 <div key={k}>90 <dt className="text-2xs uppercase tracking-wide text-ink-3">{k}</dt>91 <dd className="text-ink">{formatValue(v, spec)}</dd>92 </div>93 ))}94 </dl>95 </div>96 </div>97 );98}99