Chris Parmer — home

Stacked area with direct labels

CO₂ emissions by world region since 1850

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

Stacked area with direct labels — CO₂ emissions by world region since 1850

Python Code

"""Stacked area with direct labels — CO₂ emissions by world region since 1850.

The Our World in Data treatment: regions stacked from largest to smallest,
labelled at the right edge where the eye lands, with the axis tucked away
and a few dated events pinned to the top of the stack.
"""
from pathlib import Path

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

import numpy as np
import plotly.graph_objects as go

CHART_NUM = 14
URL = "https://raw.githubusercontent.com/owid/co2-data/master/owid-co2-data.csv"
REGIONS = [("Asia", VIOLET), ("North America", TEAL), ("Europe", GREEN), ("Africa", PINK),
           ("South America", ORANGE), ("Oceania", BLUE), ("International transport", SLATE)]
EVENTS = [(1929, "Great Depression"), (1945, "End of WWII"), (1973, "Oil crisis"), (2008, "Financial crisis"), (2020, "Covid-19")]


def generate():
    df = fetch_csv(URL, usecols=["country", "year", "co2"])
    df = df[df["country"].isin([r for r, _ in REGIONS] + ["World"]) & (df["year"] >= 1850)]
    wide = df.pivot(index="year", columns="country", values="co2").fillna(0) / 1000  # Mt → Gt
    # OWID's regions exclude international aviation and shipping; the remainder against the world total is that.
    wide["International transport"] = (wide["World"] - wide[[r for r, _ in REGIONS if r != "International transport"]].sum(axis=1)).clip(lower=0)
    wide = wide.drop(columns="World")
    order = [r for r, _ in REGIONS]
    colors = dict(REGIONS)
    fig = go.Figure()
    for r in order:
        fig.add_trace(go.Scatter(x=wide.index, y=wide[r], mode="lines", name=r, stackgroup="one",
                                 line=dict(width=0.5, color="white"), fillcolor=colors[r],
                                 hovertemplate="%{x}: %{y:.2f} Gt<extra>" + r + "</extra>"))
    # Direct labels at the right edge, at the vertical middle of each band in the last year.
    last = wide.iloc[-1]
    cum = 0
    labels = []
    for r in order:
        mid = cum + last[r] / 2
        cum += last[r]
        labels.append((r, mid, last[r]))
    # Nudge the small ones apart.
    pos = np.array([m for _, m, _ in labels])
    for _ in range(100):
        for i in range(len(pos) - 1):
            if pos[i + 1] - pos[i] < 1.6:
                pos[i + 1] = pos[i] + 1.6
    for (r, _, v), y in zip(labels, pos):
        fig.add_annotation(x=wide.index[-1], y=y, text=f"<b>{r}</b> {v:.1f} Gt", showarrow=False, xanchor="left", xshift=8,
                           font=dict(size=12, color=colors[r]))
    total = wide.sum(axis=1)
    for yr, text in EVENTS:
        fig.add_annotation(x=yr, y=total.loc[yr], text=text, showarrow=True, arrowhead=0, arrowwidth=1, arrowcolor=MUTED,
                           ax=-30 if yr >= 2000 else 0, ay=-38, xanchor="right" if yr >= 2000 else "center", font=dict(size=11, color=MUTED))

    fig.update_layout(**base_layout(margin=dict(l=60, r=210, t=110, b=70)))
    fig.update_xaxes(range=[1850, wide.index[-1]], dtick=25, showgrid=False)
    fig.update_yaxes(range=[0, 42], tickvals=[10, 20, 30, 40], ticksuffix=" Gt", title_text=None)
    titles(fig, "Annual CO₂ emissions from fossil fuels and industry, by region",
           f"Billions of tonnes of CO₂ per year, 1850–{wide.index[-1]}. Regions are stacked; the top edge is the world total.",
           "Source: Global Carbon Budget (2024), via Our World in Data's co2-data repository. Emissions are territorial; land-use change is excluded.")
    save(CHART_NUM, fig)

Made with Plotly