Real 2 m temperature — styled plot and animation¶
examples/data/europe_t2m.npz is a real ERA5 2 m air temperature stack over Europe: 45 daily frames
(°C). It is rendered with the temperature_2m style (Spectral_r) on a single fixed scale matched
to the data (the global min/max across all frames). That fixed, data-matched scale is what keeps the colours
comparable frame-to-frame and well-exposed — auto-ranging each frame (or using the full −40…40 climatology
scale) washes a summer field into a flat orange-red.
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
import cleopatra
from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, Basemap, ColorBar, Feature, FrameLabel
from cleopatra.glyphs.base.animation import embed_gif
print("cleopatra", cleopatra.__version__)
z = np.load(Path("../../../examples/data/europe_t2m.npz"), allow_pickle=True)
celsius = z["celsius"].astype(float) # (45, rows, cols) daily 2 m temperature, deg C
labels = list(z["labels"])
ext = [float(v) for v in z["extent"]]
STYLE = "temperature_2m"
# Widened past the data range so Spectral_r's pale midpoint sits at the cold edge, not on the summer data.
VMIN, VMAX = -15.0, 42.0
OCEAN_NAVY = "#1f3b57"
BASEMAP = Basemap(
relief=False,
features=[
Feature("coastline", colors="0.55", linewidths=0.5),
Feature("borders", colors="0.45", linewidths=0.4),
],
)
print("stack:", celsius.shape, "|", labels[0], "->", labels[-1],
"| fixed scale", (round(VMIN, 1), round(VMAX, 1)), "degC")
from cleopatra.styling.params import DataStyle
cleopatra 0.32.0 stack: (45, 111, 164) | Jun 01 -> Jul 15 | fixed scale (-15.0, 42.0) degC
The basemap — a "dark ocean" look. A navy frame background (full_bleed=OCEAN_NAVY) shows straight
through the masked seas — no relief, so nothing muddies the water — with Natural-Earth coastlines/borders drawn
on top. The warm land pops against the flat dark sea. The date label is white (it sits on the dark sea at
top-left); the colorbar ticks are black (they sit over the warm land beside the bar, where dark reads best).
The backdrop is a typed Basemap — Basemap(relief=False, features=[Feature("coastline", …), Feature("borders", …)]) — the same structured-parameter style as ColorBar and FrameLabel (validated fields
and IDE autocomplete). plot/animate still accept basemap=True, a plain dict, or a callable too.
A single day — the warmest in the series¶
ArrayGlyph.plot takes the same full_bleed=OCEAN_NAVY + basemap=BASEMAP as the animation below: the land
carries the temperature, the masked seas show the flat navy background, and Natural-Earth coastlines/borders
sit on top. The temperature_2m preset supplies the Spectral_r colours, and an explicit
colorbar=ColorBar(location="right", inside=True, box=False, tick_color="black") overrides the preset's
swatch with a real, tick-marked colorbar drawn straight on the frame (box=False, no backing card; the
ticks sit over the warm land beside the bar, so black reads best) — the style provides the look, the explicit
argument wins (defaults < preset < explicit).
day = int(np.argmax([np.nanmean(f) for f in celsius]))
glyph = ArrayGlyph(celsius[day], extent=[ext[0], ext[2], ext[1], ext[3]])
glyph.plot(vmin=VMIN, vmax=VMAX, full_bleed=OCEAN_NAVY, basemap=BASEMAP, colorbar=ColorBar(location="right", inside=True, box=False, tick_color="black"), data_style=DataStyle(style=STYLE))
plt.show()
Animated over the full 45-day period¶
full_bleed=OCEAN_NAVY fills the frame edge-to-edge with the dark sea colour (no chrome), and
basemap=BASEMAP lays Natural-Earth coastlines/borders on top — the opaque land shows temperature while the
masked seas read as a flat navy ocean. The temperature_2m preset again rides with an overriding
colorbar=ColorBar(location="right", inside=True, box=False, tick_color="black"), so the scale is a real
tick-marked colorbar drawn straight on the frame (box=False, no backing card). The date label is white — it
sits on the dark sea at top-left — while the colorbar ticks are black, sitting over the warm land beside the
bar. Every frame shares the fixed data-matched scale, so the warming and cooling read directly and nothing
blows out.
glyph = ArrayGlyph(celsius, extent=[ext[0], ext[2], ext[1], ext[3]])
anim = glyph.animate(labels, vmin=VMIN, vmax=VMAX, frame_label=FrameLabel(color="deeppink", size=16, location=[-11.2, 60.5]), # nudged a touch below the top edge
interval=140, full_bleed=OCEAN_NAVY, basemap=BASEMAP, colorbar=ColorBar(location="right", inside=True, box=False, tick_color="black"), data_style=DataStyle(style=STYLE))
plt.close(glyph.fig)
embed_gif(anim, fps=7)
<IPython.core.display.Image object>
The same series as a glowing plume over a real basemap — temperature_flame¶
A single ArrayGlyph.animate call does all of it now: the 45-frame loop, the temperature_flame preset on
the fixed 2…30 °C scale, the date label, the full-bleed layout (full_bleed=True — fills the frame, no
chrome, aspect-matched so there's no distortion) and the basemap (basemap=Basemap() — the typed default:
a relief backdrop plus Natural-Earth coastlines/borders, composed by zorder). The flame preset's value-linked opacity lets the cool
areas reveal the terrain while the hot plume glows on top (the CAMS look). The 2…30 °C scale rides
along as an inside colorbar — colorbar=ColorBar(location="right", inside=True, box=False, tick_color="white")
— with white ticks to read on the dark frame.
FL_VMIN, FL_VMAX = 2.0, 30.0
west, east, south, north = ext
glyph = ArrayGlyph(celsius, extent=[west, south, east, north])
anim2 = glyph.animate(labels, vmin=FL_VMIN, vmax=FL_VMAX, frame_label=FrameLabel(color="white"), title="", interval=140, full_bleed="black", basemap=Basemap(), colorbar=ColorBar(location="right", inside=True, box=False, tick_color="white"), data_style=DataStyle(style="temperature_flame"))
plt.close(glyph.fig)
embed_gif(anim2, fps=7)
<IPython.core.display.Image object>