Walter-Lieth Climate Diagram — Makie.jl

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 Makie.jl

Julia source (Makie.jl)

# anyplot.ai
# climograph-walter-lieth: Walter-Lieth Climate Diagram
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 90/100 | Created: 2026-06-15

using CairoMakie
using Colors
using Statistics

const THEME       = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const INK_MUTED   = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F"

# Temperature->red, precipitation->blue: universal climate diagram convention
# (semantic exception to Imprint palette ordinal ordering)
const TEMP_COL   = colorant"#AE3030"  # Imprint matte red
const PRECIP_COL = colorant"#4467A3"  # Imprint blue

# Station: fictional Anatolian-style semi-arid continental climate
# 1991-2020 climate normals; one frost month, clear summer arid period
const STATION  = "Anatolian Station"
const ELEV     = 894
const MLBLS    = ["J","F","M","A","M","J","J","A","S","O","N","D"]
const TEMP_ARR = Float64[-0.5, 1.2, 6.5, 12.0, 17.0, 21.0, 25.0, 25.0, 20.5, 14.0, 7.5, 2.0]
const PREC_ARR = Float64[38.0, 34.0, 40.0, 46.0, 52.0, 30.0, 14.0, 10.0, 18.0, 26.0, 34.0, 40.0]
const ANN_T    = round(mean(TEMP_ARR); digits=1)
const ANN_P    = Int(round(sum(PREC_ARR)))

const xs      = collect(1.0:12.0)
const prec_sc = PREC_ARR ./ 2.0  # Walter-Lieth 1:2 scaling (10 degC = 20 mm)

# Y limits: buffer below -10 for the frost indicator band
const Y_BOT   = -13.0
const Y_TOP   = 35.0
const FRO_BOT = -13.0
const FRO_TOP = -11.0

fig = Figure(
    size            = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

# Left axis: temperature (degrees C)
ax = Axis(
    fig[1, 1];
    title             = "climograph-walter-lieth · julia · makie · anyplot.ai",
    titlesize         = 18,
    titlecolor        = INK,
    backgroundcolor   = PAGE_BG,
    ylabel            = "Temperature (°C)",
    ylabelsize        = 15,
    ylabelcolor       = INK,
    xlabelcolor       = INK,
    xticklabelsize    = 13,
    yticklabelsize    = 13,
    xticklabelcolor   = INK_SOFT,
    yticklabelcolor   = INK_SOFT,
    xtickcolor        = INK_SOFT,
    ytickcolor        = INK_SOFT,
    leftspinecolor    = INK_SOFT,
    bottomspinecolor  = INK_SOFT,
    topspinevisible   = false,
    rightspinevisible = false,
    xgridvisible      = false,
    ygridvisible      = false,
    xticks            = (collect(1:12), MLBLS),
    yticks            = collect(-10:10:30),
)
ylims!(ax, Y_BOT, Y_TOP)

# Right axis: precipitation (mm), scaled 2:1 against the temperature axis
# ylims are set so that 10 degC on the left = 20 mm on the right
ax_r = Axis(
    fig[1, 1];
    yaxisposition      = :right,
    backgroundcolor    = :transparent,
    rightspinecolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    ytickcolor         = INK_SOFT,
    topspinevisible    = false,
    leftspinevisible   = false,
    bottomspinevisible = false,
    xticksvisible      = false,
    xticklabelsvisible = false,
    xlabelvisible      = false,
    xgridvisible       = false,
    ygridvisible       = false,
    ylabel             = "Precipitation (mm)",
    ylabelsize         = 15,
    ylabelcolor        = INK,
    yticklabelsize     = 13,
    yticks             = ([0.0, 20.0, 40.0, 60.0], ["0", "20", "40", "60"]),
)
ylims!(ax_r, Y_BOT * 2, Y_TOP * 2)

linkxaxes!(ax, ax_r)
xlims!(ax, 0.5, 12.5)

# Humid fill (blue): months where precipitation curve exceeds temperature curve
band!(ax, xs, TEMP_ARR, max.(TEMP_ARR, prec_sc); color = (PRECIP_COL, 0.40))

# Arid fill (red): months where temperature curve exceeds precipitation curve
band!(ax, xs, min.(TEMP_ARR, prec_sc), TEMP_ARR; color = (TEMP_COL, 0.30))

# Temperature line (on left axis)
lines!(ax, xs, TEMP_ARR; color = TEMP_COL, linewidth = 2.5)

# Precipitation line (on right axis, raw mm values)
lines!(ax_r, xs, PREC_ARR; color = PRECIP_COL, linewidth = 2.5)

# Dashed 0 degC reference line
hlines!(ax, [0.0]; color = (INK_SOFT, 0.5), linewidth = 1.0, linestyle = :dash)

# Frost indicator blocks for months with mean T < 0 degC
for (i, t) in enumerate(TEMP_ARR)
    if t < 0.0
        poly!(ax,
            [Point2f(i - 0.5, FRO_BOT), Point2f(i + 0.5, FRO_BOT),
             Point2f(i + 0.5, FRO_TOP), Point2f(i - 0.5, FRO_TOP)];
            color = PRECIP_COL, strokewidth = 0)
    end
end

# Station metadata header (data coordinates on left axis)
text!(ax, 1.0, 34.0;
    text     = "$(STATION)  ($(ELEV) m a.s.l.)",
    fontsize = 16,
    color    = INK,
    align    = (:left, :top),
)
text!(ax, 1.0, 29.5;
    text     = "Mean T = $(ANN_T) °C  ·  Annual P = $(ANN_P) mm  (1991–2020)",
    fontsize = 13,
    color    = INK_SOFT,
    align    = (:left, :top),
)

# Legend
leg_elems = [
    LineElement(color = TEMP_COL, linewidth = 2.5),
    LineElement(color = PRECIP_COL, linewidth = 2.5),
    PolyElement(color = (TEMP_COL, 0.30), strokecolor = :transparent),
    PolyElement(color = (PRECIP_COL, 0.40), strokecolor = :transparent),
    PolyElement(color = PRECIP_COL, strokecolor = :transparent),
]
leg_labels = [
    "Mean temperature",
    "Precipitation",
    "Arid period (T > P/2)",
    "Humid period (P/2 > T)",
    "Frost month (mean T < 0 °C)",
]
Legend(fig[1, 2], leg_elems, leg_labels;
    framecolor        = (INK_SOFT, 0.3),
    backgroundcolor   = ELEVATED_BG,
    labelcolor   = INK,
    framevisible = true,
    framewidth   = 1,
    patchsize    = (22, 12),
    rowgap       = 6,
    padding      = (10, 10, 10, 10),
)

colsize!(fig.layout, 1, Relative(0.82))

save("plot-$(THEME).png", fig; px_per_unit = 2)

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

Other implementations