Temperature as a glowing flame / plume¶
This recolours CAMS Aerosol Optical Depth look — a dark, hillshaded Earth with glowing, wispy plumes — for 2 m temperature. Two shipped data-style presets do it in one call:
temperature_flame— black → red → yellow → white (the hottest areas blow out to yellow-white).temperature_flame_amber— a warmer gold/orange ramp, less blown-out.
Each preset carries the flame colour ramp and a value-linked opacity: a pixel's opacity is tied to its value
(cool → transparent, so the dark terrain shows through; hot → opaque, glowing). Composed over a dark hillshaded
backdrop — drawn with the glyph's own add_relief / add_features — the warm areas glow while the cool ones
fade into the relief. ArrayGlyph applies the preset and runs the animation; no hand-rolled alpha_scaled_image,
FuncAnimation, or standalone cleopatra.basemap.reference calls.
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from cleopatra.glyphs.base.animation import embed_gif
from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, FrameLabel
# --- data: an ERA5-Land 2 m-temperature stack over Europe (°C, 23 daily frames) ---
npz = np.load(Path("../../../examples/data/europe_t2m.npz"), allow_pickle=True)
celsius = npz["celsius"][::2] # every other day, to keep the GIFs light
labels = list(npz["labels"])[::2]
west, south, east, north = (float(v) for v in npz["extent"])
extent = [west, east, south, north]
def add_backdrop(glyph):
"""Give the glyph a hillshaded Earth using its OWN methods.
Call it directly on the glyph (not as a `basemap=` callback). `add_relief`
creates the axes on demand (so it can come before `animate`) and draws the
global relief at true lon/lat, cropped to the view; `add_features` overlays
faint coastlines/borders. All on `glyph.ax`, no standalone helpers. The dark
canvas itself comes from the `temperature_flame*` preset (its
`background`), so nothing here paints the figure.
"""
glyph.add_relief("medium", alpha=0.45, zorder=-2) # creates the axes if needed
glyph.add_features("coastline", "50m", color="0.5", linewidth=0.5, zorder=0)
glyph.add_features("borders", "50m", color="0.35", linewidth=0.4, zorder=0)
print("stack:", celsius.shape, "| frames:", labels[0], "->", labels[-1])
from cleopatra.styling.params import DataStyle
stack: (23, 111, 164) | frames: Jun 01 -> Jul 15
Still comparison — both flame presets on the hottest day¶
Each panel draws an ArrayGlyph(celsius[-1], style=...) into a subplot with plot(ax=...), then add_backdrop
lays the hillshaded Earth underneath with the glyph's own add_relief/add_features. The preset carries the flame
ramp and the value-linked opacity, so the warm areas glow and the cool ones fade into the relief.
fig, axs = plt.subplots(1, 2, figsize=(15, 6))
fig.set_facecolor("black") # the flame preset blackens each panel's axes; the shared figure is ours to paint
for ax, style in zip(axs, ["temperature_flame", "temperature_flame_amber"]):
glyph = ArrayGlyph(celsius[-1], extent=extent)
glyph.plot(data_style=DataStyle(style=style), ax=ax, add_colorbar=False)
add_backdrop(glyph)
ax.set_title(style, color="white", loc="left", fontweight="bold")
ax.set_xticks([])
ax.set_yticks([])
fig.tight_layout()
plt.show()
Animation A — white-hot (temperature_flame)¶
The builder flow: make the glyph, add_backdrop (which creates the axes and draws the dark Earth), then animate
(which reuses that axes). FrameLabel(color="white") puts a legible date label on the black scene.
glyph = ArrayGlyph(celsius, extent=extent)
add_backdrop(glyph) # dark hillshaded backdrop, drawn before the frames
anim_a = glyph.animate(data_style=DataStyle(style="temperature_flame"),
time=labels,
add_colorbar=False,
frame_label=FrameLabel(color="white"),
)
embed_gif(anim_a, fps=8)
<IPython.core.display.Image object>
Animation B — amber (temperature_flame_amber)¶
The same builder flow with the warmer preset — the only change is style="temperature_flame_amber".
glyph = ArrayGlyph(celsius, extent=extent)
add_backdrop(glyph)
anim_b = glyph.animate(data_style=DataStyle(style="temperature_flame_amber"),
time=labels,
add_colorbar=False,
frame_label=FrameLabel(color="white"),
)
embed_gif(anim_b, fps=8)
<IPython.core.display.Image object>
The whole flame/plume look — colour ramp, value-linked glow, dark hillshaded backdrop, per-frame labels — is
ArrayGlyph(..., style="temperature_flame") with add_backdrop (the glyph's own add_relief/add_features) and
.plot() / .animate(), because the glow is baked into the preset. The temperature_flame* presets are pinned to
0…40 °C with opacity ramping in over the warm end; the generic temperature presets auto-range instead.