AI-Hydro Interactive Module · Foundations

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.

Watch the film · Reveal the six fluxes · Drag the rain slider · Scrub the cycle · Pass the checkpoint

Your path through this module

🎬
Film
💧
Fluxes
⚖️
Balance
🔎
Scrub
✓
Check
📜
Sources

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).

Tip If Manim is not installed in your kernel, skip to §4 where the interactive cycle diagram covers the same ground without any dependencies — just drag the scrubber.
Python · Manim Renders the water-cycle animation (~15 s at 480p)
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.

☀️💧
Evaporation
E
Liquid water at the ocean or lake surface gains enough energy from sunlight to escape as vapour — no plant needed.
Global mean: ~1 200 mm yr⁻¹ over ocean
🌿⬆️
Transpiration
T
Plants pump water from the soil through their roots and release it as vapour through tiny pores called stomata. Over forested land, T often exceeds direct E.
≈ 40 % of terrestrial ET
☁️❄️
Condensation
—
Rising moist air cools. When it reaches the dew point, vapour condenses onto tiny aerosols forming cloud droplets. No flux symbol — it's the process inside the cloud store.
≈ Dew point ↓ 1 °C per 100 m ascent
🌧️
Precipitation
P
Cloud droplets collide, coalesce and fall when large enough. Rain, snow, sleet, hail — all precipitation. It is the input flux in the land-surface water balance.
Global mean: ≈ 1 000 mm yr⁻¹ land
🪨⬇️
Infiltration
f
Rain that reaches the soil surface and soaks downward under gravity and capillary suction. Infiltrated water may become groundwater or be taken up by roots — it does not reach the stream immediately.
Rate: 1–300 mm hr⁻¹ depending on soil
🏔️➡️🌊
Runoff
R
Rain (or snowmelt) that does not infiltrate flows overland as surface runoff. Lateral groundwater that emerges at the stream bank becomes baseflow. Together they are the discharge, Q.
World rivers: ≈ 40 000 km³ yr⁻¹
The balance equation Over a long enough period for a closed catchment: P = ET + R + ΔS Precipitation equals evapotranspiration plus runoff plus the change in storage. This single equation underlies everything in the next six modules.

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.

P 3.0 mm/d
ET 2.0 mm/d
R (overflow) 0.0 mm/d
ΔS / day +1.0

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.

condensation Evaporation Transpiration Condensation Precipitation Infiltration Runoff ◀ Drag the scrubber to step through each flux ▶

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?

Those move water downward, not into the atmosphere.
Infiltration goes underground; condensation forms clouds — neither is an upward flux to the air.

2 · Over many years, the water balance for a closed catchment is: P equals…

Runoff only ignores ET and changes in soil/groundwater storage.
ET only ignores runoff and storage — valid only if R = 0 and ΔS = 0, which never holds in practice.

3 · Condensation happens because rising moist air…

Sunlight warms surfaces, not air above the clouds. Condensation is driven by cooling, not heating.
While cold air plays a role in some weather, the primary mechanism is the adiabatic cooling of a rising parcel.

Sources & Further Reading

  1. Dingman, S. L. (2015). Physical Hydrology, 3rd ed. Waveland Press. — The mass-balance equation and flux definitions follow Chapter 2.
  2. USGS Water Science School. The Water Cycle — Plain-language flux descriptions and global magnitudes.
  3. 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
CC-BY-4.0