stats: /stats/history and History, Politics and Culture sections on NL, Gouda, Curaçao, US and KR pages (step 4)
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
Long-run charts (Maddison, HYDE, UN via OWID; CBS and Pew religion), a filterable sourced timeline, Wikidata office holders and turnout, Nager.Date holidays, UNESCO sites, Wikidata people by sitelinks, curated food and cultural exports. Wikidata labels now fall back to mul.
This commit is contained in:
@@ -22,6 +22,10 @@ const staticPaths = [
|
||||
'/stats/countries/CUW',
|
||||
'/stats/elections',
|
||||
'/stats/opinions',
|
||||
'/stats/history',
|
||||
'/stats/countries/NLD',
|
||||
'/stats/countries/USA',
|
||||
'/stats/countries/KOR',
|
||||
'/trackers/books',
|
||||
'/trackers/spotting',
|
||||
'/trackers/cars',
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Globe from 'lucide-svelte/icons/globe';
|
||||
import Vote from 'lucide-svelte/icons/vote';
|
||||
import MessageCircle from 'lucide-svelte/icons/message-circle';
|
||||
import ScrollText from 'lucide-svelte/icons/scroll-text';
|
||||
let { children } = $props();
|
||||
const icons = {
|
||||
overview: ChartLine,
|
||||
@@ -17,7 +18,8 @@
|
||||
gouda: House,
|
||||
countries: Globe,
|
||||
elections: Vote,
|
||||
opinions: MessageCircle
|
||||
opinions: MessageCircle,
|
||||
history: ScrollText
|
||||
} as const;
|
||||
const tabs = [
|
||||
{ name: 'overview', href: '/stats' },
|
||||
@@ -103,6 +105,9 @@
|
||||
.tab-elections {
|
||||
background: linear-gradient(135deg, #06b6d4, #3b82f6);
|
||||
}
|
||||
.tab-history {
|
||||
background: linear-gradient(135deg, #b45309, #eab308);
|
||||
}
|
||||
.tab-opinions {
|
||||
background: linear-gradient(135deg, #ec4899, #f97316);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import Palmtree from 'lucide-svelte/icons/tree-palm';
|
||||
import Vote from 'lucide-svelte/icons/vote';
|
||||
import MessageCircle from 'lucide-svelte/icons/message-circle';
|
||||
import ScrollText from 'lucide-svelte/icons/scroll-text';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
|
||||
let { data } = $props();
|
||||
@@ -88,6 +89,13 @@
|
||||
grad: 'linear-gradient(135deg,#06b6d4,#3b82f6)',
|
||||
text: 'Every Dutch general election since 1918, Gouda down to the polling station, and the US presidential vote by state.'
|
||||
},
|
||||
{
|
||||
href: '/stats/history',
|
||||
title: 'History and culture',
|
||||
icon: ScrollText,
|
||||
grad: 'linear-gradient(135deg,#b45309,#eab308)',
|
||||
text: 'The Netherlands, Gouda, Curaçao, the US and Korea over the long run, a sourced timeline, office holders, religion, holidays, UNESCO sites and famous people.'
|
||||
},
|
||||
{
|
||||
href: '/stats/opinions',
|
||||
title: 'Opinions',
|
||||
|
||||
@@ -2,6 +2,8 @@ import { error, redirect } from '@sveltejs/kit';
|
||||
import { cached, loadSeries } from '#lib/server/stats.js';
|
||||
import { isCountryPage, PROFILE_SERIES } from '#lib/stats/countries.js';
|
||||
import { CW_SERIES } from '#lib/stats/curacao.js';
|
||||
import { isHeritage } from '#lib/stats/heritage.js';
|
||||
import { loadHeritage } from '#lib/server/stats-history.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, url }) => {
|
||||
@@ -9,6 +11,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
|
||||
if (code !== params.iso3 && isCountryPage(code))
|
||||
redirect(308, `/stats/countries/${code}${url.search}`);
|
||||
if (!isCountryPage(code)) error(404, 'Not Found');
|
||||
const heritage = isHeritage(code) ? loadHeritage(code) : null;
|
||||
const series = await cached(`stats:country:${code}`, () =>
|
||||
loadSeries([
|
||||
...PROFILE_SERIES.map((id) => `${id}:${code}`),
|
||||
@@ -22,5 +25,5 @@ export const load: PageServerLoad = async ({ params, url }) => {
|
||||
...(code === 'CUW' ? CW_SERIES : [])
|
||||
])
|
||||
);
|
||||
return { code, series };
|
||||
return { code, series, heritage: heritage ? await heritage : null };
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { COUNTRY } from '#lib/stats/regions.js';
|
||||
import { COUNTRY_COLOR, FLAG } from '#lib/stats/countries.js';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import PlaceHeritage from '#lib/stats/PlaceHeritage.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
const c = $derived(data.code);
|
||||
@@ -713,6 +714,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.heritage}
|
||||
<PlaceHeritage h={data.heritage} attributions={a} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.back {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { cached, loadCbsCross, loadSeries } from '#lib/server/stats.js';
|
||||
import { epSeriesIds, GOUDA_PC4 } from '#lib/stats/energylabels.js';
|
||||
import { loadHeritage } from '#lib/server/stats-history.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
// Gouda (GM0513) and its neighbours; NL01 is the national total.
|
||||
@@ -20,7 +21,7 @@ const KWB_MEASURES = [
|
||||
];
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const [series, wijken] = await Promise.all([
|
||||
const [series, wijken, heritage] = await Promise.all([
|
||||
cached('stats:gouda', () =>
|
||||
loadSeries([
|
||||
...['NL01', ...RING].flatMap((r) => [
|
||||
@@ -40,7 +41,8 @@ export const load: PageServerLoad = async () => {
|
||||
),
|
||||
cached('stats:gouda:kwb', () =>
|
||||
loadCbsCross(KWB_TABLE, KWB_MEASURES, 'WijkenEnBuurten', ['WK0513', 'GM0513', 'NL00'])
|
||||
)
|
||||
),
|
||||
loadHeritage('GM0513')
|
||||
]);
|
||||
return { series, wijken, kwbYear: 2024 };
|
||||
return { series, wijken, kwbYear: 2024, heritage };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import StatChart from '#lib/stats/StatChart.svelte';
|
||||
import PlaceHeritage from '#lib/stats/PlaceHeritage.svelte';
|
||||
import Kpi from '#lib/stats/Kpi.svelte';
|
||||
import { barRankOption, change, latest, lineOption, type StatPoint } from '#lib/stats/chart.js';
|
||||
import { compact, num, period } from '#lib/stats/format.js';
|
||||
@@ -415,6 +416,8 @@
|
||||
<p class="empty">Neighbourhood figures are not loaded yet.</p>
|
||||
{/if}
|
||||
|
||||
<PlaceHeritage h={data.heritage} attributions={a} />
|
||||
|
||||
<style>
|
||||
tr.total td {
|
||||
font-weight: 600;
|
||||
|
||||
4
src/routes/stats/history/+page.server.ts
Normal file
4
src/routes/stats/history/+page.server.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { loadHistoryOverview } from '#lib/server/stats-history.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => loadHistoryOverview();
|
||||
203
src/routes/stats/history/+page.svelte
Normal file
203
src/routes/stats/history/+page.svelte
Normal file
@@ -0,0 +1,203 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import StatChart from '#lib/stats/StatChart.svelte';
|
||||
import EventTimeline from '#lib/stats/EventTimeline.svelte';
|
||||
import { barRankOption, lineOption, type Line, type StatPoint } from '#lib/stats/chart.js';
|
||||
import { HERITAGE, indexTo } from '#lib/stats/heritage.js';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
|
||||
let { data } = $props();
|
||||
const s = $derived(data.series);
|
||||
const a = $derived(data.attributions);
|
||||
const pts = (id: string): StatPoint[] => s[id]?.points ?? [];
|
||||
const COUNTRIES = ['NLD', 'USA', 'KOR'] as const;
|
||||
const WORLD = { name: 'World', color: '#94a3b8', dashed: true };
|
||||
const lines = (prefix: string, withCw = false, cwId = ''): Line[] => [
|
||||
...COUNTRIES.map((c) => ({
|
||||
name: HERITAGE[c].name,
|
||||
points: pts(`${prefix}:${c}`),
|
||||
color: HERITAGE[c].color
|
||||
})),
|
||||
...(withCw
|
||||
? [{ name: 'Curaçao', points: pts(cwId || `${prefix}:CUW`), color: HERITAGE.CUW.color }]
|
||||
: []),
|
||||
{ ...WORLD, points: pts(`${prefix}:WLD`) }
|
||||
];
|
||||
const popIndex = $derived([
|
||||
...COUNTRIES.map((c) => ({
|
||||
name: HERITAGE[c].name,
|
||||
points: indexTo(pts(`owid:hist:population:${c}`), '1950'),
|
||||
color: HERITAGE[c].color
|
||||
})),
|
||||
{
|
||||
name: 'Curaçao',
|
||||
points: indexTo(pts('owid:cw:population'), '1950'),
|
||||
color: HERITAGE.CUW.color
|
||||
},
|
||||
{
|
||||
name: 'Gouda',
|
||||
points: indexTo(pts('cbs:pop:jan1:GM0513'), '1950'),
|
||||
color: HERITAGE.GM0513.color
|
||||
},
|
||||
{ ...WORLD, points: indexTo(pts('owid:hist:population:WLD'), '1950') }
|
||||
]);
|
||||
const unaffiliated = $derived(
|
||||
(['NLD', 'USA', 'KOR', 'CUW', 'WLD'] as const)
|
||||
.map((c) => ({
|
||||
label: c === 'WLD' ? 'World' : HERITAGE[c].name,
|
||||
value:
|
||||
pts(`pew:religion:religiously_unaffiliated:${c}`).find(([d]) =>
|
||||
d.startsWith('2020')
|
||||
)?.[1] ?? NaN,
|
||||
highlight: c === 'NLD'
|
||||
}))
|
||||
.filter((r) => Number.isFinite(r.value))
|
||||
);
|
||||
const places = Object.values(HERITAGE);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>History · stats · Stan Runge</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="The Netherlands, Gouda, Curaçao, the US and South Korea over the long run: population, income, life expectancy, urbanisation and religion, plus a sourced timeline of key events."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<section class="stats-intro">
|
||||
<span class="stats-eyebrow">stats / history</span>
|
||||
<h1>History and culture</h1>
|
||||
<p>
|
||||
Five places over the long run: the Netherlands, Gouda, Curaçao, the United States and South
|
||||
Korea. Long series come from the Maddison Project, HYDE and the UN via Our World in Data, CBS
|
||||
and Pew; the timeline is curated, and every event links to its source. Each place's own page has
|
||||
its office holders, holidays, UNESCO sites, well-known people and food.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="places">
|
||||
{#each places as p (p.code)}
|
||||
<a class="place" href={`${resolve(p.href as '/stats')}#history`} style:--c={p.color}>
|
||||
<strong>{p.name}</strong>
|
||||
<span>history, politics and culture <ArrowRight size={13} /></span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<h2 class="stats-section-title">The long run</h2>
|
||||
<div class="stats-grid">
|
||||
<StatChart
|
||||
title="GDP per person, long run"
|
||||
subtitle="Maddison Project Database 2023 · international $ at 2011 prices"
|
||||
sources={['owid']}
|
||||
attributions={a}
|
||||
note="Curaçao is not in the Maddison database."
|
||||
option={(t) => lineOption(t, lines('owid:maddison'), { unit: '$', decimals: 0, zoom: true })}
|
||||
/>
|
||||
<StatChart
|
||||
title="Life expectancy, long run"
|
||||
subtitle="years at birth · Riley, Zijdeman et al., HMD and UN WPP via Our World in Data"
|
||||
sources={['owid']}
|
||||
attributions={a}
|
||||
option={(t) =>
|
||||
lineOption(t, lines('owid:hist:life-expectancy', true, 'owid:cw:life-expectancy'), {
|
||||
unit: 'years',
|
||||
decimals: 1
|
||||
})}
|
||||
/>
|
||||
<StatChart
|
||||
title="Urbanisation since 1500"
|
||||
subtitle="% living in urban areas · HYDE 2023 via Our World in Data"
|
||||
sources={['owid']}
|
||||
attributions={a}
|
||||
option={(t) => lineOption(t, lines('owid:hist:urban-long'), { unit: '%', decimals: 1 })}
|
||||
/>
|
||||
<StatChart
|
||||
title="Population growth since 1950"
|
||||
subtitle="index, 1950 = 100 · OWID (UN WPP), CBS for Gouda"
|
||||
sources={['owid', 'cbs']}
|
||||
attributions={a}
|
||||
option={(t) => lineOption(t, popIndex, { decimals: 0 })}
|
||||
/>
|
||||
<StatChart
|
||||
title="Population since 1500"
|
||||
subtitle="inhabitants · HYDE, Gapminder and UN via Our World in Data"
|
||||
sources={['owid']}
|
||||
attributions={a}
|
||||
option={(t) =>
|
||||
lineOption(
|
||||
t,
|
||||
COUNTRIES.map((c) => ({
|
||||
name: HERITAGE[c].name,
|
||||
points: pts(`owid:hist:population:${c}`),
|
||||
color: HERITAGE[c].color
|
||||
})),
|
||||
{ decimals: 0 }
|
||||
)}
|
||||
/>
|
||||
<StatChart
|
||||
title="No religion, 2020"
|
||||
subtitle="% of the population religiously unaffiliated · Pew Research Center"
|
||||
sources={['pew']}
|
||||
attributions={a}
|
||||
height="240px"
|
||||
option={(t) => barRankOption(t, unaffiliated, { unit: '%', decimals: 1, color: '#64748b' })}
|
||||
/>
|
||||
<StatChart
|
||||
title="The Netherlands loses its religion"
|
||||
subtitle="% of adults without religious affiliation, and Catholics · CBS since 1849"
|
||||
sources={['cbs']}
|
||||
attributions={a}
|
||||
option={(t) =>
|
||||
lineOption(
|
||||
t,
|
||||
[
|
||||
{ name: 'No religion', points: pts('cbs:religion:37944:none:NL01'), color: '#64748b' },
|
||||
{
|
||||
name: 'Roman Catholic',
|
||||
points: pts('cbs:religion:37944:catholic:NL01'),
|
||||
color: '#e11d48'
|
||||
},
|
||||
{
|
||||
name: 'Dutch Reformed',
|
||||
points: pts('cbs:religion:37944:hervormd:NL01'),
|
||||
color: '#f97316'
|
||||
}
|
||||
],
|
||||
{ unit: '%', decimals: 1 }
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="stats-section-title">Timeline</h2>
|
||||
<EventTimeline events={data.events} showPlaces />
|
||||
<p class="stats-note">
|
||||
{data.events.length} curated events, each summarised from and linked to its source (mostly Wikipedia,
|
||||
CC BY-SA). Click an event for the summary and source; filter by theme or place.
|
||||
</p>
|
||||
|
||||
<style>
|
||||
.places {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.place {
|
||||
border: 1px solid var(--site-border);
|
||||
border-left: 4px solid var(--c);
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.7rem 0.9rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.place span {
|
||||
color: var(--site-muted);
|
||||
font-size: 0.78rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user