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.

""" anyplot.ai
climograph-walter-lieth: Walter-Lieth Climate Diagram
Library: letsplot 4.10.1 | Python 3.13.13
Quality: 84/100 | Created: 2026-06-15
"""
import os
import numpy as np
import pandas as pd
from lets_plot import (
LetsPlot,
aes,
element_blank,
element_line,
element_rect,
element_text,
geom_hline,
geom_line,
geom_point,
geom_ribbon,
geom_segment,
geom_text,
ggplot,
ggsize,
labs,
scale_color_manual,
scale_linetype_manual,
scale_x_continuous,
scale_y_continuous,
theme,
)
from lets_plot.export import ggsave
LetsPlot.setup_html()
# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
# Imprint palette — semantic mapping: temperature=red, precipitation=blue
TEMP_COLOR = "#AE3030" # matte red (pos 5) — heat/temperature semantic
PRECIP_COLOR = "#4467A3" # blue (pos 3) — water/precipitation semantic
# Ribbon alpha: higher in dark mode for visibility on near-black background
ribbon_alpha = 0.28 if THEME == "light" else 0.50
# Athens, Greece — classic Mediterranean climate, 1991-2020 normals
month_labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
month_num = np.arange(1, 13)
temp_c = np.array([9.3, 10.2, 12.5, 16.4, 21.4, 26.1, 29.0, 28.8, 24.4, 19.3, 14.4, 10.7])
precip_mm = np.array([57, 37, 37, 23, 15, 7, 6, 7, 15, 51, 56, 71])
# Walter-Lieth 1:2 convention: 20 mm precipitation ↔ 10°C (precip_scaled = precip / 2)
precip_scaled = precip_mm / 2.0
# Fill regions — zero-height ribbons are invisible, so no masking needed
humid_top = np.maximum(temp_c, precip_scaled) # = precip_scaled where humid, else = temp_c
arid_bottom = np.minimum(temp_c, precip_scaled) # = precip_scaled where arid, else = temp_c
df = pd.DataFrame(
{
"month": month_num,
"temp": temp_c,
"precip_scaled": precip_scaled,
"humid_top": humid_top,
"arid_bottom": arid_bottom,
}
)
# Long-format data for legend-mapped lines and points
df_lines = pd.concat(
[
pd.DataFrame({"month": month_num, "y": temp_c, "series": "Temperature"}),
pd.DataFrame({"month": month_num, "y": precip_scaled, "series": "Precipitation"}),
],
ignore_index=True,
)
# Manual right y-axis (precipitation scale) — sec_axis not available in lets-plot 4.x
x_rax = 12.55 # right axis vertical line
x_rtick = 12.72 # tick mark end and label start
# Walter-Lieth convention: 0, 20, 40, 60, 100 mm ticks
right_y = [0, 10, 20, 30, 50] # °C-scale positions (mm/2)
right_labels = ["0", "20", "40", "60", "100"] # mm values
df_rax_line = pd.DataFrame({"x": [x_rax], "xend": [x_rax], "y": [-4], "yend": [51]})
df_rtick_marks = pd.DataFrame({"x": [x_rax] * 5, "xend": [x_rax + 0.13] * 5, "y": right_y, "yend": right_y})
df_rtick_labels = pd.DataFrame({"x": [x_rtick] * 5, "y": right_y, "label": right_labels})
df_rax_title = pd.DataFrame({"x": [14.2], "y": [23.0], "label": ["Precipitation (mm)"]})
# Title fontsize: scaled for long title string
title_text = "Athens, Greece · climograph-walter-lieth · python · letsplot · anyplot.ai"
title_fs = max(11, round(16 * 67 / len(title_text)))
subtitle_text = "107 m a.s.l. │ Tmean = 18.5°C │ P = 383 mm yr⁻¹ │ 1991–2020 normals"
anyplot_theme = theme(
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
panel_background=element_rect(fill=PAGE_BG),
panel_border=element_blank(),
panel_grid_major=element_line(color=INK_SOFT, size=0.2),
panel_grid_minor=element_blank(),
axis_title=element_text(color=INK, size=12),
axis_text=element_text(color=INK_SOFT, size=10),
axis_line=element_line(color=INK_SOFT),
plot_title=element_text(color=INK, size=title_fs),
plot_subtitle=element_text(color=INK_SOFT, size=9),
legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
legend_text=element_text(color=INK_SOFT, size=10),
legend_title=element_text(color=PAGE_BG, size=1),
legend_position="bottom",
)
plot = (
ggplot(df, aes(x="month"))
# Humid fill (blue): precipitation curve above temperature curve (Jan-Mar, Oct-Dec)
+ geom_ribbon(aes(ymin="temp", ymax="humid_top"), fill=PRECIP_COLOR, alpha=ribbon_alpha)
# Arid fill (red): temperature curve above precipitation curve (Apr-Sep)
+ geom_ribbon(aes(ymin="arid_bottom", ymax="temp"), fill=TEMP_COLOR, alpha=ribbon_alpha)
# Freezing-point reference
+ geom_hline(yintercept=0, color=INK_SOFT, size=0.5, linetype="dashed")
# Temperature and precipitation curves (color + linetype → CVD-safe combined legend)
+ geom_line(data=df_lines, mapping=aes(x="month", y="y", color="series", linetype="series"), size=1.2)
+ geom_point(data=df_lines, mapping=aes(x="month", y="y", color="series"), size=2.2)
+ scale_color_manual(values={"Temperature": TEMP_COLOR, "Precipitation": PRECIP_COLOR}, name=" ")
+ scale_linetype_manual(values={"Temperature": "solid", "Precipitation": "dashed"}, name=" ")
# X-axis: month labels; extended range accommodates manual right axis
+ scale_x_continuous(breaks=list(range(1, 13)), labels=month_labels, limits=(0.4, 15.5))
# Left y-axis: temperature (°C) — extended to 53 to show 100mm right-axis tick
+ scale_y_continuous(name="Temperature (°C)", breaks=[0, 10, 20, 30, 40], limits=(-5, 53))
# Manual right y-axis — precipitation (mm), color-coded blue
+ geom_segment(data=df_rax_line, mapping=aes(x="x", xend="xend", y="y", yend="yend"), color=INK_SOFT, size=0.4)
+ geom_segment(data=df_rtick_marks, mapping=aes(x="x", xend="xend", y="y", yend="yend"), color=INK_SOFT, size=0.4)
+ geom_text(data=df_rtick_labels, mapping=aes(x="x", y="y", label="label"), color=PRECIP_COLOR, size=3.0, hjust=0)
+ geom_text(
data=df_rax_title, mapping=aes(x="x", y="y", label="label"), color=PRECIP_COLOR, size=3.5, angle=270, hjust=0.5
)
+ labs(x="", title=title_text, subtitle=subtitle_text)
+ anyplot_theme
+ ggsize(800, 450)
)
ggsave(plot, f"plot-{THEME}.png", path=".", scale=4)
ggsave(plot, f"plot-{THEME}.html", path=".")
Part of Walter-Lieth Climate Diagram on anyplot.ai.