SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
5.0 KB · 99 lines tsx
Raw Blame History
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