Chris Parmer — home

Bump chart

the ten most populous countries, ranked every decade since 1950

Example from Plotly for highly customizable print-ready data visualization · shared helpers

Bump chart — the ten most populous countries, ranked every decade since 1950

Python Code

"""Bump chart — the ten most populous countries, ranked every decade since 1950.

Rank on the y axis, time on the x, and a smoothed line per country so the
overtakes read as crossings. Names at both ends, the two countries that
swapped first place in colour, everything else in grey.
"""
from pathlib import Path

from _shared import fetch_csv, save, base_layout, titles, INK, MUTED, FAINT, GRID, VIOLET, TEAL, PINK, GREEN, hex_to_rgba

import numpy as np
import plotly.graph_objects as go

CHART_NUM = 18
URL = "https://ourworldindata.org/grapher/population.csv?csvType=full&useColumnShortNames=true"
YEARS = [1950, 1960, 1970, 1980, 1990, 2000, 2010, 2020, 2023]
TOP = 10
HIGHLIGHT = {"India": VIOLET, "China": TEAL, "Nigeria": GREEN, "United States": PINK}


def generate():
    df = fetch_csv(URL)
    df = df[df["code"].notna() & (df["code"].str.len() == 3) & (df["code"] != "OWID_WRL") & df["year"].isin(YEARS)]
    df["rank"] = df.groupby("year")["population_historical"].rank(ascending=False, method="first")
    df = df[df["rank"] <= TOP]
    countries = sorted(df["entity"].unique(), key=lambda c: df[df["entity"] == c]["rank"].min())

    fig = go.Figure()
    for c in countries:
        g = df[df["entity"] == c].sort_values("year")
        color = HIGHLIGHT.get(c, FAINT)
        hot = c in HIGHLIGHT
        # Break the line where a country drops out of the top ten.
        xs, ys = [], []
        for yr in YEARS:
            row = g[g["year"] == yr]
            xs.append(yr); ys.append(row["rank"].iloc[0] if len(row) else None)
        fig.add_trace(go.Scatter(x=xs, y=ys, mode="lines+markers", name=c, connectgaps=False,
                                 line=dict(color=color, width=3.5 if hot else 2, shape="spline", smoothing=0.8),
                                 marker=dict(size=10 if hot else 7, color="white", line=dict(color=color, width=2.5 if hot else 1.5)),
                                 hovertemplate=f"{c}<br>%{{x}}: #%{{y}}<extra></extra>"))
        for side, yr, anchor, shift in [("first", YEARS[0], "right", -14), ("last", YEARS[-1], "left", 14)]:
            row = g[g["year"] == yr]
            if len(row):
                pop = row["population_historical"].iloc[0] / 1e6
                fig.add_annotation(x=yr, y=row["rank"].iloc[0], text=f"{c}  <span style='color:{MUTED}'>{pop:,.0f}M</span>" if side == "last" else f"<span style='color:{MUTED}'>{pop:,.0f}M</span>  {c}",
                                   showarrow=False, xanchor=anchor, xshift=shift, font=dict(size=12, color=color if hot else INK))
    fig.update_layout(**base_layout(margin=dict(l=220, r=200, t=110, b=60)))
    fig.update_xaxes(tickvals=YEARS, showgrid=True, gridcolor=GRID, range=[1946, 2027], tickfont=dict(size=12))
    fig.update_yaxes(showticklabels=False, showgrid=False, range=[TOP + 0.6, 0.4])
    for i in (1, 5, 10):
        fig.add_annotation(x=0, y=i, xref="paper", text=f"#{i}", showarrow=False, xanchor="right", xshift=-170, font=dict(size=11, color=FAINT))
    titles(fig, "India overtook China as the most populous country in 2023",
           "The ten largest countries by population, ranked at each decade. Population in millions at each end.",
           "Source: UN World Population Prospects (2024) and HYDE, via Our World in Data.")
    save(CHART_NUM, fig)

Made with Plotly