Sustainable Energy for All

2026
Country-level energy data from the UN Sustainable Energy for All initiative, covering electricity access, energy efficiency, and renewable share from 1990 to 2010.
Author
Published

Tuesday, the 26th of May, 2026

See the code that generated this plot.
// Gribouille is imported by the typst-render preamble (see assets/typst/_preamble.typ);
// do not import it here or the theme-* wrappers get rebound.
// #import "@preview/gribouille:0.1.0": *
// #import "@local/gribouille:0.0.0": *
// #set page(width: 18cm, height: 9.45cm, margin: 0cm)

#let num(s) = if s == "NA" or s == "" { 0.0 } else { float(s) }

#let raw = csv("data/energy_cleaned.csv", row-type: dictionary)

// Four mutually exclusive, exhaustive slices of the renewable total (% of final
// energy). One dictionary keeps each category bound to its Okabe-Ito colour, so
// labels, the stacking order, and the legend can never drift out of sync.
// Okabe-Ito is colourblind-safe; warm "old" biomass through cool "new" turbines.
#let cat-colours = (
  "Traditional biomass": rgb("#e69f00"),
  "Modern bioenergy": rgb("#009e73"),
  "Hydropower": rgb("#56b4e9"),
  "Wind & solar": rgb("#0072b2"),
)
#let cats = cat-colours.keys()

// One-line definition per category, aligned with `cats`. Each rolls up the
// raw columns summed in `reshape`, so the legend names what every band is.
#let cat-labels = (
  [#strong[Traditional biomass] \ wood, charcoal & dung for cooking],
  [#strong[Modern bioenergy] \ biogas, biofuels & waste-to-energy],
  [#strong[Hydropower] \ electricity from flowing water],
  [#strong[Wind & solar] \ plus geothermal & marine],
)

#let accent = rgb("#0f8b8d")

// Box-styled callout: a translucent paper background plus a hairline accent
// border lifts the prose off the stacked bands so it stays legible. The paper
// colour comes from the ambient `page.fill` (typst-render sets the page fill to
// the document background), mirroring how ink is read from `text.fill`, so the
// boxes track the light / dark site toggle.
#let callout(body) = context {
  // `page.fill` is `auto` when the document sets no page fill (standalone
  // compile); typst-render sets it to the background, so fall back to white.
  let bg = if page.fill in (auto, none) { white } else { page.fill }
  box(
    fill: bg.transparentize(12%),
    inset: (x: 5pt, y: 3pt),
    radius: 3pt,
    stroke: 0.5pt + accent.transparentize(45%),
  )[#body]
}

// One row per (year, category) carrying the stacked band bounds (ymin, ymax).
// geom-area draws every band from y = 0, so stacked areas would paint over one
// another; geom-ribbon with explicit bounds is the honest stacking primitive.
#let reshape(country) = {
  raw
    .filter(row => row.country_name == country)
    .map(row => {
      let y = float(row.yr)
      let shares = (
        "Traditional biomass": num(row.traditional_biomass_consumption_tfec_pct),
        "Modern bioenergy": num(row.modern_biomass_energy_consumption_tfec_pct)
          + num(row.biogas_consumption_tfec_pct)
          + num(row.liquid_biofuels_energy_consumption_tfec_pct)
          + num(row.waste_energy_consumption_tfec_pct),
        "Hydropower": num(row.hydro_energy_consumption_tfec_pct),
        "Wind & solar": num(row.wind_energy_consumption_tfec_pct)
          + num(row.solar_energy_consumption_tfec_pct)
          + num(row.geothermal_energy_consumption_tfec_pct)
          + num(row.marine_energy_consumption_tfec_pct),
      )
      cats
        .fold((acc: 0.0, out: ()), (st, c) => {
          let hi = st.acc + shares.at(c)
          (
            acc: hi,
            out: st.out + ((year: y, category: c, ymin: st.acc, ymax: hi),),
          )
        })
        .out
    })
    .flatten()
}

#let vn = reshape("Vietnam")
#let de = reshape("Germany")

#let panel(rows, country, annotations) = defer(
  plot,
  data: rows,
  mapping: aes(x: "year", ymin: "ymin", ymax: "ymax", fill: "category"),
  layers: (geom-ribbon(alpha: 1),) + annotations,
  scales: (
    scale-x-continuous(breaks: (1990, 2000, 2010)),
    scale-fill-discrete(limits: cats, palette: cat-colours.values(), labels: cat-labels),
  ),
  labs: labs(title: country, x: none, y: "% Final Energy", fill: none),
  theme: theme-minimal(),
)

// In-panel callouts: the total renewable share at each end, plus a one-line
// "why" sitting in the empty corner of each panel. Text colour is left to the
// theme ink so the callouts track the light / dark site toggle.
#let vn-annot = (
  annotate("label", x: 1990.2, y: 79, label: "76%", size: 9pt, anchor: "west"),
  annotate("label", x: 2009.8, y: 42, label: "35%", size: 9pt, anchor: "east"),
  annotate(
    "typst",
    x: 2009.6,
    y: 70,
    label: callout[
      #text(
        fill: cat-colours.at("Traditional biomass"),
        weight: "semibold"
      )[Traditional biomass] #emph[for cooking] \
      #emph[fades as fossil fuels spread]
    ],
    size: 6.5pt,
    anchor: "east",
  ),
)
#let de-annot = (
  annotate("label", x: 1990.2, y: 2.5, label: "2%", size: 9pt, anchor: "west"),
  annotate("label", x: 2009.8, y: 11.4, label: "11%", size: 9pt, anchor: "east"),
  annotate(
    "typst",
    x: 1990.2,
    y: 9.6,
    label: callout[
      #text(
        fill: cat-colours.at("Modern bioenergy"),
        weight: "semibold"
      )[Modern bioenergy],
      #emph[then]
      #text(
        fill: cat-colours.at("Wind & solar"),
        weight: "semibold"
      )[wind & solar], \
      #emph[built from almost nothing]
    ],
    size: 6.5pt,
    anchor: "west",
  ),
)

#let panels = compose(
  panel(vn, "Vietnam", vn-annot),
  panel(de, "Germany", de-annot),
  layout: "grid",
  columns: 2,
  collect: ("fill",),
  guides: guides(default: guide-legend(position: "bottom")),
  gutter: 1cm,
  labs: labs(
    title: "What \"Renewable Energy\" Means Depends on How Rich a Country Is",
    subtitle: "Renewable share of final energy use, by source, 1990 to 2010.",
    caption: typst([
      Source: Sustainable Energy for All (TidyTuesday 2026-05-26). \
      Author: #link("https://mickael.canouil.fr")[Mickaël CANOUIL].
    ])
  ),
)

#box[
  #panels
  #place(top + center, dx: 0pt, dy: 2cm)[
    #set align(center)
    #set text(size: 8pt, style: "italic", fill: accent)
    As incomes rise, \ two roads diverge
    #v(2pt)
    #box(inset: (x: 2pt))[
      #text(size: 40pt, fill: cat-colours.at("Traditional biomass"))[]
      #h(4pt)
      #text(size: 40pt, fill: cat-colours.at("Wind & solar"))[]
    ]
  ]
]

Two stacked-area panels of renewable energy by source, 1990 to 2010. Vietnam’s traditional biomass band shrinks and its renewable share falls from 76% to 35%, while Germany’s modern bioenergy and wind and solar grow and its share rises from 2% to 11%.

Two stacked-area panels of renewable energy by source, 1990 to 2010. Vietnam’s traditional biomass band shrinks and its renewable share falls from 76% to 35%, while Germany’s modern bioenergy and wind and solar grow and its share rises from 2% to 11%.

Figure 1: Renewable energy by source as a share of total final energy consumption, 1990 to 2010, for Vietnam and Germany.

Data: TidyTuesday 2026-05-26.

The Sustainable Energy for All dataset tracks each country’s energy mix from 1990 to 2010. A high renewable share is not always a sign of clean, modern energy. Across countries in 2010, the renewable share correlates 0.88 with the share coming from traditional biomass, that is wood, charcoal, and dung burned for cooking and heating.

Vietnam shows what happens as a poor country grows richer. Its renewable share falls from 76% to 35%, not because it abandons clean energy, but because fossil fuels expand faster than traditional biomass can be replaced.

Germany shows the opposite road. Starting from almost nothing, it builds modern bioenergy and then wind and solar, lifting its renewable share from 2% to 11%. The same headline number hides two very different stories.

Back to top