Files
website/src/lib/stats/views/CountriesView.svelte

171 lines
5.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>