The Water Cycle in Motion
Every drop of rain that ever fell on your field started its journey here. Watch a water molecule trace the six fluxes that move it from ocean to cloud to stream — and learn why the whole thing is just a giant closed balance sheet.
Your path through this module
1 · Watch the cycle unfold
The animation below shows a parcel of water making one full lap of the hydrologic cycle — from the ocean surface through the atmosphere and back to the sea. Click Run to render it (requires Manim in the kernel environment; see the note if it's absent).
from manim import *
class WaterCycle(Scene):
def construct(self):
self.camera.background_color = "#0a0a15"
C, B, G, V = "#00FFFF", "#00A3FF", "#34d399", "#a78bfa"
# ── Static scene ──────────────────────────────────────
sea = Rectangle(width=14, height=2.2,
fill_color=B, fill_opacity=0.55, stroke_width=0)
sea.move_to(DOWN * 3.2)
land = Polygon(
[-7, -2, 0], [7, -2, 0], [7, -4, 0], [-7, -4, 0],
fill_color="#1a2b1a", fill_opacity=1, stroke_width=0
)
hill = Polygon(
[-0.5, -2, 0], [1.5, -0.9, 0], [3.5, -2, 0],
fill_color="#223322", fill_opacity=1, stroke_width=0
)
sun = Circle(radius=0.55,
fill_color="#FFD060", fill_opacity=0.9, stroke_width=0)
sun.move_to(UP * 3.4 + RIGHT * 5.8)
c1 = Ellipse(width=2.8, height=1.2,
fill_color=WHITE, fill_opacity=0.85, stroke_width=0)
c2 = Ellipse(width=1.6, height=1.1,
fill_color=WHITE, fill_opacity=0.85, stroke_width=0)
c3 = Ellipse(width=1.6, height=1.1,
fill_color=WHITE, fill_opacity=0.85, stroke_width=0)
c2.shift(LEFT * 0.8 + UP * 0.4)
c3.shift(RIGHT * 0.8 + UP * 0.4)
cloud = VGroup(c1, c2, c3).move_to(UP * 1.8 + LEFT * 1.5)
trunk = Rectangle(width=0.15, height=0.45,
fill_color="#6b4c2a", fill_opacity=1, stroke_width=0)
trunk.move_to(RIGHT * 3.1 + DOWN * 1.45)
foliage = Triangle(fill_color="#2d6a2d",
fill_opacity=0.9, stroke_width=0).scale(0.45)
foliage.move_to(trunk.get_top() + UP * 0.3)
tree = VGroup(trunk, foliage)
self.add(sea, land, hill, sun, cloud, tree)
# ── Phase 1: Evaporation ───────────────────────────────
evap = VGroup(*[
Arrow(sea.get_top() + RIGHT * x,
sea.get_top() + RIGHT * x + UP * 2.2,
color=B, stroke_width=2.5, buff=0.05,
max_tip_length_to_length_ratio=0.18)
for x in [-2.5, -1.2, 0, 1.2, 2.5]
])
e_lbl = Text("Evaporation", font_size=20, color=B)
e_lbl.move_to(LEFT * 4.5 + DOWN * 0.8)
self.play(LaggedStart(*[GrowArrow(a) for a in evap], lag_ratio=0.15),
run_time=1.4)
self.play(FadeIn(e_lbl, shift=RIGHT * 0.3), run_time=0.5)
self.wait(0.4)
# ── Phase 2: Transpiration ─────────────────────────────
tr = VGroup(*[
Arrow(tree.get_top() + RIGHT * (i * 0.18 - 0.18),
tree.get_top() + RIGHT * (i * 0.18 - 0.18) + UP * 1.0,
color=G, stroke_width=2.2, buff=0.05,
max_tip_length_to_length_ratio=0.22)
for i in range(3)
])
t_lbl = Text("Transpiration", font_size=18, color=G)
t_lbl.next_to(tree, RIGHT, buff=0.25).shift(UP * 0.5)
self.play(LaggedStart(*[GrowArrow(a) for a in tr], lag_ratio=0.2),
run_time=0.9)
self.play(FadeIn(t_lbl, shift=UP * 0.2), run_time=0.4)
self.wait(0.4)
# ── Phase 3: Condensation ──────────────────────────────
cond_glow = cloud.copy().set_stroke(color=C, width=2.5)
c_lbl = Text("Condensation", font_size=20, color="#ddddff")
c_lbl.next_to(cloud, UP, buff=0.3)
self.play(Create(cond_glow), FadeIn(c_lbl, shift=UP * 0.25),
run_time=0.9)
self.wait(0.4)
# ── Phase 4: Precipitation ─────────────────────────────
rain = VGroup(*[
Line(cloud.get_bottom() + RIGHT * (i * 0.55 - 1.1),
cloud.get_bottom() + RIGHT * (i * 0.55 - 1.1) + DOWN * 1.6,
color=B, stroke_width=2.8)
for i in range(5)
])
p_lbl = Text("Precipitation", font_size=20, color=B)
p_lbl.next_to(cloud, LEFT, buff=0.35).shift(DOWN * 0.3)
self.play(LaggedStart(*[Create(r) for r in rain], lag_ratio=0.1),
run_time=1.1)
self.play(FadeIn(p_lbl, shift=LEFT * 0.2), run_time=0.4)
self.wait(0.4)
# ── Phase 5: Infiltration + Runoff ─────────────────────
inf = VGroup(*[
Arrow(hill.get_top() + RIGHT * (i * 0.3 - 0.3) + UP * 0.2,
hill.get_top() + RIGHT * (i * 0.3 - 0.3) + DOWN * 0.55,
color=V, stroke_width=2.2, buff=0.0,
max_tip_length_to_length_ratio=0.25)
for i in range(3)
])
i_lbl = Text("Infiltration", font_size=18, color=V)
i_lbl.next_to(hill, LEFT, buff=0.25).shift(DOWN * 0.1)
ro = Arrow(hill.get_right() + DOWN * 0.1,
sea.get_top() + RIGHT * 4.5,
color=C, stroke_width=3, buff=0.05)
r_lbl = Text("Runoff", font_size=18, color=C)
r_lbl.next_to(ro.get_center(), DOWN, buff=0.2)
self.play(LaggedStart(*[GrowArrow(a) for a in inf], lag_ratio=0.15),
run_time=0.8)
self.play(FadeIn(i_lbl, shift=LEFT * 0.2), run_time=0.35)
self.play(GrowArrow(ro), FadeIn(r_lbl, shift=DOWN * 0.2), run_time=0.7)
self.wait(0.4)
# ── Phase 6: Close the loop ────────────────────────────
loop = ArcBetweenPoints(
sea.get_left() + UP * 0.7,
evap[0].get_start(),
angle=-TAU / 5, color=C, stroke_width=2.5
)
loop.add_tip(tip_length=0.2)
title = Text("The Hydrologic Cycle", font_size=24, color=WHITE)
title.move_to(DOWN * 0.6 + LEFT * 3.5)
self.play(Create(loop), run_time=1.0)
self.play(
FadeIn(title, shift=UP * 0.2),
*[lbl.animate.set_color(C)
for lbl in [e_lbl, t_lbl, c_lbl, p_lbl, i_lbl, r_lbl]],
run_time=1.3
)
self.wait(1.3)
2 · The six fluxes — one by one
The hydrologic cycle is simply six named arrows connecting four stores: the ocean, the atmosphere, the soil, and the snowpack. Scroll down to reveal each flux.
3 · The bathtub: mass balance in action
Imagine the catchment as a leaky bucket. Rain fills it from above; evaporation and runoff drain it. Drag the Precipitation slider to fill faster or slower — watch storage rise, overflow as runoff, or empty during a drought.
Fixed in this simple model — in reality ET varies with temperature, humidity, and vegetation.
4 · Scrub the cycle — stop time on any flux
The controls below drive a cycle diagram frame-by-frame. Each stage illuminates one flux so you can linger exactly where you want. Works with or without Manim.
5 · Checkpoint
Three quick questions — get all three right to mark this module complete and unlock the next two.
1 · Which two processes return water vapour to the atmosphere?
2 · Over many years, the water balance for a closed catchment is: P equals…
3 · Condensation happens because rising moist air…
Sources & Further Reading
- Dingman, S. L. (2015). Physical Hydrology, 3rd ed. Waveland Press. — The mass-balance equation and flux definitions follow Chapter 2.
- USGS Water Science School. The Water Cycle — Plain-language flux descriptions and global magnitudes.
- Trenberth, K. E., Smith, L., Qian, T., Dai, A., & Fasullo, J. (2007). Estimates of the Global Water Budget and Its Annual Cycle Using Observational and Model Data. Journal of Hydrometeorology, 8(4), 758–769. — Source for global mean evaporation and runoff magnitudes.
- Author
- Mohammad Galib · Purdue University
- License
- CC-BY-4.0 — free to reuse with attribution
- Module ID
water-cycle-in-motion· v0.1.1- Course
- Introduction to Hydrology ·
intro-hydro-2026 - Data sources
- No external data — all illustrations are synthetic / schematic
- Animation
- Manim Community v0.18+ (optional); falls back gracefully