All checks were successful
Deploy production / deploy (push) Successful in 1m19s
171 lines
5.1 KiB
Svelte
171 lines
5.1 KiB
Svelte
<script lang="ts">
|
||
import type { CountriesViewData } from '#lib/server/stats-views.js';
|
||
import type { StatsCommon } from '#lib/stats/matrix.js';
|
||
import { resolve } from '$app/paths';
|
||
import StatChart from '#lib/stats/StatChart.svelte';
|
||
import { barRankOption, lineOption, type StatPoint } from '#lib/stats/chart.js';
|
||
import { compact, num } from '#lib/stats/format.js';
|
||
import { COUNTRY } from '#lib/stats/regions.js';
|
||
import { COMPARE_COLUMNS, COUNTRY_COLOR, FLAG } from '#lib/stats/countries.js';
|
||
|
||
let { data }: { data: CountriesViewData & StatsCommon } = $props();
|
||
const s = $derived(data.series);
|
||
const a = $derived(data.attributions);
|
||
const pts = (id: string): StatPoint[] => s[id]?.points ?? [];
|
||
const name = (c: string) => COUNTRY[c] ?? c;
|
||
const HIGHLIGHT = ['NLD', 'CUW', 'USA', 'KOR', 'CHN', 'WLD'];
|
||
|
||
function show(v: { value: number } | null, fmt: string) {
|
||
if (!v) return '–';
|
||
if (fmt === 'compact') return compact(v.value);
|
||
if (fmt === 'usd') return `$${compact(v.value)}`;
|
||
if (fmt === 'years') return num(v.value, 1);
|
||
return `${num(v.value, 1)}%`;
|
||
}
|
||
const rank = (key: string) =>
|
||
data.rows
|
||
.filter((r) => r.values[key])
|
||
.map((r) => ({
|
||
label: `${FLAG[r.code] ?? ''} ${name(r.code)}`,
|
||
value: r.values[key]!.value,
|
||
highlight: r.code === 'NLD' || r.code === 'CUW'
|
||
}));
|
||
const gdpLines = $derived(
|
||
HIGHLIGHT.map((c) => ({
|
||
name: name(c),
|
||
points: pts(`wb:NY.GDP.PCAP.PP.KD:${c}`),
|
||
color: COUNTRY_COLOR[c]
|
||
})).filter((l) => l.points.length)
|
||
);
|
||
const lifeLines = $derived(
|
||
HIGHLIGHT.map((c) => ({
|
||
name: name(c),
|
||
points: pts(`wb:SP.DYN.LE00.IN:${c}`),
|
||
color: COUNTRY_COLOR[c]
|
||
})).filter((l) => l.points.length)
|
||
);
|
||
const newestYear = (key: string) =>
|
||
data.rows.reduce((m, r) => {
|
||
const p = r.values[key]?.period;
|
||
return p && p > m ? p : m;
|
||
}, '');
|
||
</script>
|
||
|
||
<section class="stats-intro">
|
||
<span class="stats-eyebrow">stats / countries</span>
|
||
<h1>Countries</h1>
|
||
<p>
|
||
The places I compare most, side by side. Latest actual values (no forecasts) from the World Bank
|
||
and the IMF World Economic Outlook; the IMF is used where it covers a country. Click a country
|
||
for its full profile.
|
||
</p>
|
||
</section>
|
||
|
||
<div class="table-wrap">
|
||
<table class="stats-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Country</th>
|
||
{#each COMPARE_COLUMNS as col (col.key)}<th class="num">{col.label}</th>{/each}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{#each data.rows as r (r.code)}
|
||
<tr class:hl={r.code === 'NLD' || r.code === 'CUW'}>
|
||
<td>
|
||
<a
|
||
class="country-link"
|
||
href={resolve(`/stats/${r.code.toLowerCase()}/overview` as '/stats')}
|
||
><span class="flag">{FLAG[r.code]}</span>{name(r.code)}</a
|
||
>
|
||
</td>
|
||
{#each COMPARE_COLUMNS as col (col.key)}
|
||
{@const v = r.values[col.key]}
|
||
<td class="num" title={v ? `${v.period.slice(0, 4)} · ${v.source}` : ''}
|
||
>{show(v, col.fmt)}<small>{v ? ` '${v.period.slice(2, 4)}` : ''}</small></td
|
||
>
|
||
{/each}
|
||
</tr>
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="stats-note">
|
||
Small numbers after each value are the year it refers to. GDP per capita in constant 2021
|
||
international dollars (PPP). Growth, inflation, unemployment and debt from the IMF where
|
||
available, otherwise the World Bank.
|
||
</p>
|
||
|
||
<div class="stats-grid">
|
||
<StatChart
|
||
title="GDP per capita, PPP"
|
||
subtitle={`constant 2021 international $ · ${newestYear('gdppc').slice(0, 4)}`}
|
||
sources={['worldbank']}
|
||
attributions={a}
|
||
height="420px"
|
||
option={(t) => barRankOption(t, rank('gdppc'), { decimals: 0, unit: '$', color: '#10b981' })}
|
||
/>
|
||
<StatChart
|
||
title="Life expectancy at birth"
|
||
subtitle={`years · ${newestYear('life').slice(0, 4)}`}
|
||
sources={['worldbank']}
|
||
attributions={a}
|
||
height="420px"
|
||
option={(t) => barRankOption(t, rank('life'), { decimals: 1, unit: 'yr', color: '#a855f7' })}
|
||
/>
|
||
<StatChart
|
||
title="GDP per capita over time"
|
||
subtitle="PPP, constant 2021 international $"
|
||
sources={['worldbank']}
|
||
attributions={a}
|
||
option={(t) => lineOption(t, gdpLines, { unit: '$', decimals: 0 })}
|
||
/>
|
||
<StatChart
|
||
title="Life expectancy since 1960"
|
||
subtitle="years at birth"
|
||
sources={['worldbank']}
|
||
attributions={a}
|
||
option={(t) => lineOption(t, lifeLines, { unit: 'years', decimals: 1, min: 'dataMin' })}
|
||
/>
|
||
<StatChart
|
||
title="Government debt"
|
||
subtitle="% of GDP · IMF where available, else World Bank (central government)"
|
||
sources={['imf', 'worldbank']}
|
||
attributions={a}
|
||
height="420px"
|
||
option={(t) => barRankOption(t, rank('debt'), { decimals: 0, unit: '%', color: '#e11d48' })}
|
||
/>
|
||
<StatChart
|
||
title="Inflation"
|
||
subtitle="% consumer prices, latest actual year"
|
||
sources={['imf', 'worldbank']}
|
||
attributions={a}
|
||
height="420px"
|
||
option={(t) => barRankOption(t, rank('infl'), { decimals: 1, unit: '%', color: '#f97316' })}
|
||
/>
|
||
</div>
|
||
|
||
<style>
|
||
.country-link {
|
||
display: inline-flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
color: var(--site-text);
|
||
text-decoration: none;
|
||
font-weight: 550;
|
||
}
|
||
.country-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.flag {
|
||
font-size: 1.1rem;
|
||
}
|
||
tr.hl td {
|
||
background: color-mix(in srgb, var(--site-text) 4%, transparent);
|
||
}
|
||
td small {
|
||
color: var(--site-muted);
|
||
font-size: 0.68rem;
|
||
}
|
||
</style>
|