spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import { t } from '@/i18n';2import { fixed, isNum } from '@/lib/format';3import { seriesVar } from './palette';45/**6 * Age structure as a single stacked bar (0–14 / 15–64 / 65+ shares, % of population). Ordinal colouring7 * (one hue, three lightness steps via the sequential ramp) because the categories are ordered.8 * A 2 px surface gap separates segments; labels inside only when the segment is wide enough.9 */10export function PopulationPyramid({11 shares,12 name,13 year,14 className,15}: {16 shares: { young: number | null; working: number | null; old: number | null };17 name: string;18 year?: number | null;19 className?: string;20}) {21 const parts = [22 { key: 'young', label: '0–14', value: shares.young, color: 'var(--seq-2)' },23 { key: 'working', label: '15–64', value: shares.working, color: 'var(--seq-4)' },24 { key: 'old', label: '65+', value: shares.old, color: 'var(--seq-6)' },25 ];26 const valid = parts.filter((p) => isNum(p.value));27 if (valid.length === 0) return <p className="text-sm text-ink-3">{t('chart.noData')}</p>;28 const total = valid.reduce((a, p) => a + (p.value as number), 0) || 100;29 const summary = t('chart.summary.pyramid', { name, parts: valid.map((p) => `${p.label}: ${fixed(p.value as number, 1)} %`).join(', ') });30 return (31 <figure className={className}>32 <div className="flex h-6 w-full overflow-hidden rounded-xs" role="img" aria-label={summary} style={{ gap: 2 }}>33 {valid.map((p) => {34 const pct = ((p.value as number) / total) * 100;35 return (36 <div key={p.key} className="relative h-full" style={{ width: `${pct}%`, background: p.color }} title={`${p.label}: ${fixed(p.value as number, 1)} %`}>37 {pct > 14 ? (38 <span className="tnum absolute inset-0 grid place-items-center text-2xs font-medium" style={{ color: p.key === 'young' ? 'var(--ink)' : '#fff' }}>39 {fixed(p.value as number, 0)} %40 </span>41 ) : null}42 </div>43 );44 })}45 </div>46 <figcaption className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-xs text-ink-2">47 {valid.map((p) => (48 <span key={p.key} className="inline-flex items-center gap-1.5">49 <span aria-hidden className="inline-block h-2.5 w-2.5 rounded-xs" style={{ background: p.color }} />50 {p.label} <span className="tnum text-ink">{fixed(p.value as number, 1)} %</span>51 </span>52 ))}53 {year ? <span className="text-ink-3">{year}</span> : null}54 </figcaption>55 {seriesVar(0) ? null : null}56 </figure>57 );58}59