An interactive scrollytelling data story exploring why the world's happiest countries also have stubborn suicide rates. The Nordic five (Finland, Denmark, Norway, Iceland, Sweden) sit at the top of every happiness ranking, yet their suicide rates are higher than the global trend would predict. The piece walks through the evidence, peels back common explanations (age structure, year-over-year trends), and ends with a free-exploration view across 24 countries and five years.
Built as a static site with D3.js v7 and vanilla HTML/CSS/JS — no build step, no server, no API keys.
data_visualization/
├── index.html # Page skeleton, scroll sections, CDN links, citation
├── styles.css # Design tokens, layout, scroll-step rules, a11y utilities
├── script.js # Data loading, parsing, all renderers, scroll triggers
├── README.md
└── data/
├── Suicide_data_Nordic_Countries.csv # WHO age-standardized rates
├── 2015.csv 2016.csv 2017.csv 2018.csv 2019.csv # World Happiness Report
| Step | Chart | Point |
|---|---|---|
| Top | Animated choropleth (2015–2019 happiness) | Five of the happiest countries cluster in the same corner of the map, year after year. |
| 1 | Horizontal stacked bars (top 10 by 2019 happiness, factor breakdown) | The happiest countries score well across every factor the report measures. |
| 2 | Scatter + OLS trend with Nordic convex hull and a Finland vs Costa Rica key insight | The Nordics sit above the trend, and age-standardization rules out demographics. |
| 3 | Two line charts (Nordics, 2015–2019) | As Finland's happiness rose, so did its suicide rate. |
| 4 | Stacked paired bars (Finland vs Denmark) | Same happiness tier, persistent ~5-point suicide gap. |
| 5 | Grouped bars by sex (2019, Nordics) | Almost the entire gap is male. |
| Bottom | Linked exploration view | Country dropdown + year slider + WHO region filter. |
- Scroll-triggered reveals for steps Top through 5 (
IntersectionObserver). - Hover tooltips on every chart mark — country, year, happiness score, suicide rate, and CI band note when uncertainty is wide.
- D3 transitions for line draws and bar growth.
- Filters in the exploration section — country, year (slider), WHO region.
The site uses fetch to load the CSVs, so file:// will not work. Start a
static server, then open the printed URL.
# Option A — Node (no install)
npx serve
# Option B — Node alternative
npx http-server -p 8000
# Option C — Python 3 (if you have a real Python install, not the Windows Store stub)
python -m http.server 8000Then visit http://localhost:<port> in Chrome, Firefox, or Safari.
The project is plain static files, so any static host works with zero configuration.
- Open https://app.netlify.com/drop
- Drag the
data_visualizationfolder onto the drop zone. - Netlify returns a public URL like
https://<random>.netlify.app.
npm i -g vercel
vercel # accept all defaults — it will detect a static sitegit init
git add .
git commit -m "Nordic paradox data story"
git remote add origin https://github.com/<you>/<repo>.git
git push -u origin mainThen on GitHub: Settings → Pages → Source: main branch, root folder.
The site appears at https://<you>.github.io/<repo>/.
All three deployment paths require no edits to the code — every asset
path in index.html and script.js is relative.
- HTML5 / CSS3 / vanilla JavaScript (ES2020+)
- D3.js v7 — loaded from
cdn.jsdelivr.net - topojson-client v3 — for the world map
- world-atlas v2 TopoJSON — countries-110m
No bundlers. No frameworks. No tracking.
- Semantic colour. Gray for neutral marks, teal for the Nordic cluster. Highlights stay below ~10% of marks where possible.
- Colorblind-safe sex palette. Step 5 uses Okabe-Ito blue (
#0072B2) for Male and vermillion (#D55E00) for Female. Bar values are also labelled in text so colour is never the sole channel. - Colorblind-safe factor palette. Step 1's stacked bars use six muted colours from Paul Tol's qualitative palette so the factor segments remain distinguishable for the most common forms of colour-vision deficiency.
- No dual-axis tricks. Step 4 (Finland vs Denmark) uses two stacked panels sharing the year axis instead of a single chart with two y-axes.
- Uncertainty. Iceland gets a WHO confidence-interval band on the Step 3 line chart so the wide range on its suicide rate is visible alongside the trend.
- Accessibility. A visually-hidden
<aside id="alt-text">summarises the Step 1 stacked bar chart for screen readers; controls are keyboard-reachable; uncertainty is conveyed by band/opacity in addition to colour.
- Happiness: World Happiness Report, 2015–2019. One CSV per year.
- Suicide: World Health Organization, The Global Health Observatory — age-standardized suicide rates per 100,000 population. https://www.who.int/data/gho/data/indicators/indicator-details/GHO/age-standardized-suicide-rates-(per-100-000-population)
The 24 countries used throughout: Finland, Denmark, Norway, Iceland, Sweden, Canada, Australia, Costa Rica, Israel, United Kingdom, Germany, United States, Mexico, France, Spain, Brazil, Colombia, Italy, Lithuania, South Korea, Japan, Pakistan, Russia, Indonesia.
- Year-aware happiness column drift. 2015–2016 use
Country+Happiness Score; 2017 usesCountry+Happiness.Score; 2018–2019 useCountry or region+Score. - Country name normalisation on the suicide data so it joins the happiness data: Republic of Korea → South Korea, United Kingdom of Great Britain and Northern Ireland → United Kingdom, United States of America → United States, Russian Federation → Russia.
- Sex filter discipline. Every chart except Step 5 uses
Dim1ValueCode === SEX_BTSX. Step 5 usesSEX_MLEandSEX_FMLEonly. Sex categories are never mixed within a single chart. - Region filter in the exploration view uses
ParentLocationfrom the suicide dataset (consistent across years), not the happiness CSV's region column (inconsistent across years).