Walter-Lieth Climate Diagram — Pygal

A Walter-Lieth climate diagram (climograph) is the canonical visualization of a location's annual climate, plotting monthly mean temperature and monthly precipitation over the twelve months on a single panel. It uses the classic 1:2 axis-scaling convention where 10 °C on the temperature axis aligns with 20 mm on the precipitation axis, so the relationship between the two curves directly reveals water availability: where precipitation exceeds the temperature curve the period is humid (filled blue/hatched), and where the temperature curve rises above precipitation the period is arid (filled red/dotted). A header carries station metadata and annual means, while frost indicators along the baseline mark cold months.

Walter-Lieth Climate Diagram rendered with Pygal

Python source (Pygal)

""" anyplot.ai
climograph-walter-lieth: Walter-Lieth Climate Diagram
Library: pygal 3.1.0 | Python 3.13.13
Quality: 85/100 | Created: 2026-06-15
"""

import os
import sys


# Prevent the local pygal.py from shadowing the installed pygal package
_script_dir = os.path.dirname(os.path.abspath(__file__))
if _script_dir in sys.path:
    sys.path.remove(_script_dir)

import pygal
from pygal.style import Style


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Semantic color exception: precipitation = blue (water), temperature = red (heat)
PRECIP_COLOR = "#4467A3"
TEMP_COLOR = "#AE3030"

# Station: Athens, Greece — Mediterranean Csa (1991-2020 normals)
station = "Athens, Greece"
elevation = 107
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
temp_c = [9.3, 10.3, 12.4, 16.3, 21.0, 26.1, 28.7, 28.5, 24.1, 19.2, 14.3, 10.7]
precip_mm = [48, 36, 33, 23, 17, 7, 6, 5, 11, 46, 54, 57]

temp_annual = round(sum(temp_c) / 12, 1)
precip_annual = sum(precip_mm)

# Walter-Lieth: 10 °C = 20 mm → scale precipitation onto the temperature axis
precip_scaled = [p / 2 for p in precip_mm]

# Overlap base: the region below BOTH curves gets no fill (masked with background)
base = [min(p, t) for p, t in zip(precip_scaled, temp_c, strict=True)]

title_str = "climograph-walter-lieth · python · pygal · anyplot.ai"
station_meta = f"{station} ({elevation} m a.s.l.)   T = {temp_annual} °C   P = {precip_annual} mm"

title_fs = max(44, round(66 * 67 / len(title_str)))

# Colors tuple ordered by series add() sequence:
# 1. PRECIP_COLOR — humid fill (blue, bottom layer)
# 2. TEMP_COLOR   — arid fill  (red, covers blue where temp > precip)
# 3. PAGE_BG      — background mask (hides fill in overlap region 0→min)
# 4. PRECIP_COLOR — precipitation outline line
# 5. TEMP_COLOR   — temperature outline line
colors_tuple = (PRECIP_COLOR, TEMP_COLOR, PAGE_BG, PRECIP_COLOR, TEMP_COLOR)

custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=colors_tuple,
    title_font_size=title_fs,
    label_font_size=40,
    major_label_font_size=44,
    legend_font_size=44,
    value_font_size=0,
    stroke_width=4.0,
)

chart = pygal.Line(
    width=3200,
    height=1800,
    style=custom_style,
    title=title_str,
    x_title=station_meta,
    y_title="Temperature (°C)  /  Precipitation ÷ 2 (mm)",
    show_legend=True,
    legend_at_bottom=True,
    fill=False,
    show_x_guides=False,
    show_y_guides=True,
    interpolate="cubic",
    dots_size=0,
    range=(0, 35),
)

chart.x_labels = months
chart.y_labels = [0, 10, 20, 30]

# Differential fill — 3-layer stacking trick:
#   Layer 1 (blue, bottom): 0 → precip_scaled everywhere
#   Layer 2 (red, on top):  0 → temp_c everywhere (covers blue where temp > precip)
#   Layer 3 (bg mask, top): 0 → min(precip, temp) — erases fill in the shared base region
# Net result: blue between temp and precip/2 (humid), red between precip/2 and temp (arid)
chart.add("Humid period", precip_scaled, fill=True, stroke_style={"width": 0})
chart.add("Arid period", temp_c, fill=True, stroke_style={"width": 0})
chart.add(" ", base, fill=True, stroke_style={"width": 0})

# Outline lines rendered on top of fills
chart.add("Precipitation (mm ÷ 2)", precip_scaled, fill=False, stroke_style={"width": 4})
chart.add("Temperature (°C)", temp_c, fill=False, stroke_style={"width": 6})

chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Walter-Lieth Climate Diagram on anyplot.ai.

Other implementations