5.4.17 · D2Scientific Computing (Python)

Visual walkthrough — 3D plots — surface, wireframe

2,059 words9 min readBack to topic

We will answer one question the whole way down: how does a rule (two numbers in, one number out) turn into a solid you can see?

Prerequisites we lean on (each explained here, links for depth): meshgrid and broadcasting, NumPy vectorization, Matplotlib figure and axes objects. This is a child of 3D Plots — Surface & Wireframe.


Step 1 — The floor: two lists of numbers

WHAT. We begin with two ordinary 1D arrays. Think of them as tick marks along two edges of a floor.

  • how many x ticks (the length of x).
  • how many y ticks (the length of y).
  • Each is a position along the left–right edge; each is a position along the front–back edge.

WHY. A height rule needs a place to stand. One list alone only gives points on a line — but a surface lives over a 2D floor, so we need every combination, not just the two edges.

PICTURE. The two arrays sit on the two edges of an empty floor. Nothing is filled in yet — that's the whole point.

Figure — 3D plots — surface, wireframe

Step 2 — meshgrid fills in every crossing

WHAT. np.meshgrid(x, y) takes the two edge lists and produces two full 2D tables, X and Y, both of shape .

Concretely, with x = [1,2,3] () and y = [10,20] ():

  • In , the value changes as you move across a row (varies with the column x changes).
  • In , the value changes as you move down a column (varies with the row y changes).
  • Read the same cell out of both tables and you get the full floor coordinate .

WHY this tool, not a loop? We could write two nested for loops. But NumPy stores these as flat arrays and repeats them by pure copying — that's broadcasting (meshgrid and broadcasting), which is far faster and lets the height rule apply to the whole table at once (NumPy vectorization). We pick meshgrid because it hands us the two coordinate tables in the exact shape the plotter later demands.

PICTURE. Watch x get stamped down each row to make X, and y get stamped across each column to make Y. Every crossing now knows its own .

Figure — 3D plots — surface, wireframe

Step 3 — The height rule turns coordinates into altitudes

WHAT. Now apply the function to the whole coordinate tables in one shot:

Each symbol:

  • , — the two coordinate tables from Step 2, shape .
  • means "square every cell of " — elementwise, still shape .
  • — the height to stick above crossing .

Because and have identical shape, the arithmetic lands cell-by-cell and automatically inherits shape . No loop wrote this — the whole array computed at once.

WHY. A surface is nothing but a rule "here is how high the floor rises at each spot." is that height map. We compute it vectorized so its shape can never accidentally disagree with — a mismatch is the classic bug (see Step 6).

PICTURE. Above each crossing, a little vertical pole grows to height . The floor grid sprouts a forest of poles of different lengths.

Figure — 3D plots — surface, wireframe

Step 4 — Drape a sheet: plot_surface

WHAT. We connect the tops of neighbouring poles into little four-cornered patches, then fill and color them:

ax.plot_surface(X, Y, Z, cmap='viridis')
  • Arguments are (X, Y, Z) — remember floor, floor, sky.
  • cmap='viridis' maps each patch's height to a color (Colormaps in Matplotlib): low = purple, high = yellow.
  • Four neighbouring poles form one filled quad facet.

WHY a surface (vs just poles)? Isolated poles don't read as a shape. Filling the gaps between four tops produces a continuous sheet your eye reads as a solid — and coloring by height doubles as a built-in contour reading.

PICTURE. The poles from Step 3 get their tops stitched together into shaded patches; the forest becomes a smooth colored bowl.

Figure — 3D plots — surface, wireframe

Step 5 — Drape a net instead: plot_wireframe and stride

WHAT. Same poles, but now connect the tops with lines only — no fill:

ax.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='teal')
  • rstride = row stride — keep every 5th grid row line.
  • cstride = column stride — keep every 5th grid column line.
  • color= is a single value: a line can't carry a per-facet colormap, so cmap is not used here.

WHY stride at all? A grid draws 50 lines each way — a black blur. Drawing every 5th line thins the cage so you can see through it and compare front to back. Readability beats raw detail.

PICTURE. On the left, the dense cage (unreadable); on the right, rstride=5, cstride=5 gives a clean see-through net over the same bowl.

Figure — 3D plots — surface, wireframe

Step 6 — The degenerate cases (what breaks and why)

WHAT. Three failure modes, each a picture of a mismatch:

  1. Passing 1D x, y, z directly. The plotter expects 2D tables; 1D inputs have no rows/columns to form quads shape error.
  2. Manually built Z of the wrong shape. If you looped and produced instead of , the sheet drapes transposed — garbled.
  3. Forgot projection='3d'. A plain 2D axes has no plot_surface method at all AttributeError.

WHY show these. Every one comes from breaking the Step 3 invariant , or from asking a 2D axes to do 3D work. Seeing the broken picture next to the right one fixes the reflex.

PICTURE. Side-by-side: a correctly draped grid vs a transposed, twisted one — same numbers, wrong shape.

Figure — 3D plots — surface, wireframe

Step 7 — Reading a height back off the surface (forecast-then-verify)

WHAT. Take the ripple . Let be distance from the origin on the floor.

  • At the origin: , so — the sheet touches the floor.
  • Where : — the first crest ring.
  • Where : — the sheet crosses the floor again (a trough begins).

Each symbol: turns the two floor coordinates into one radius; turns that radius into a rising-then-falling height, so the surface ripples in rings.

WHY. The plot is only drawn — so we can predict a feature (crest at radius ) and then look to confirm it. That loop is how you trust a figure.

PICTURE. The rippling surface with the crest ring at marked, and the flat contact point at the origin.

Figure — 3D plots — surface, wireframe

The one-picture summary

The entire pipeline in a single frame: two edge lists → meshgrid tables → height table → draped sheet / net.

Figure — 3D plots — surface, wireframe
Recall Feynman: retell the whole walkthrough in plain words

We started with two little rulers, one for left–right, one for front–back. meshgrid walked across the floor and wrote down, at every crossing, both its x-coordinate (in table X) and its y-coordinate (in table Y) — same shape, (rows = how many y, cols = how many x). Then the height rule looked at each crossing and grew a pole of height Z = f(X,Y), all poles at once, no loops. To see it, we stitched the pole-tops into filled colored patches (a surface) or into a see-through net (a wireframe), thinning the net with strides so it isn't a blur. Everything only works because all three tables share the same shape — break that and the sheet twists or the plotter errors. Finally, since the picture is literally just the rule drawn, we can predict a bump (crest at radius ) and go check it.


Active Recall

What two things must be true of Z before plot_surface accepts it?
It must be 2D and have shape equal to X.shape == Y.shape == (len(y), len(x)).
Why does a surface facet need a 2×2 block of grid cells?
One filled quad is bounded by four neighbouring pole-tops; fewer cells leave no complete quad to fill.
In the table X, along which direction does the value change?
Across a row (i.e. with the column index) — x varies column-to-column.
For Z = sin(sqrt(X**2+Y**2)), at what floor radius is the first crest, and what height?
At , height .
What goes wrong if you build Z as (m,n) instead of (n,m)?
The surface drapes transposed/garbled because Z.shape no longer matches the coordinate grids.
Why can't a wireframe use a colormap?
It's drawn as lines, not filled patches, so it carries a single color= value, not per-facet colors.

Connections

  • Parent: 3D Plots — Surface & Wireframe
  • meshgrid and broadcasting
  • NumPy vectorization
  • Matplotlib figure and axes objects
  • Colormaps in Matplotlib
  • Contour plots
  • 2D plots — line, scatter

Concept Map

input

input

X table n by m

Y table n by m

f applied

f applied

fill quads

connect lines

thin

height to color

x edge list len m

np.meshgrid

y edge list len n

X coords per cell

Y coords per cell

Z heights n by m

plot_surface colored

plot_wireframe net

rstride cstride

cmap