DEV Community

Lucian (LKB)
Lucian (LKB)

Posted on Originally published at lkforge.com

Domain coloring: plotting complex functions f(z) in the browser

An ordinary graph maps a number to a number, so a curve fits on a flat screen. A complex function maps a plane to a plane — two inputs, two outputs, four dimensions — and there's no curve to draw.

So we do what mathematicians do on paper: we paint the plane. Here's how domain coloring works, the small evaluator behind it, and how it stays interactive in a browser tab without WebGL. Everything below is live in the free LK Forge graphing calculator — press f(z) and follow along.

Two numbers, two channels of colour

Every output f(z) is a complex number, and a complex number has exactly two visible parts: an angle (its argument) and a size (its modulus). Domain coloring spends one visual channel on each:

  • Hue = the argument of f(z) — red one way, cyan the opposite way, a full colour wheel for a full turn.
  • Brightness = the modulus — dark where f(z) is near zero, bright where it grows large.

That one rule makes the features that matter jump out. A zero is a dark spot with the whole colour wheel wrapped around it. A pole (where the function runs to infinity) is a bright spot, also ringed by every hue. Faint contour rings mark each doubling of the modulus, so you read growth like a topographic map reads elevation.

The evaluator: the same parser, a different number

The 2D graph and the complex plane share one expression parser — a shunting-yard compiler with no eval() anywhere. The only thing that changes is what sits on the evaluation stack:

  • Real graph → the stack holds ordinary numbers.
  • f(z) → the stack holds {re, im} pairs, and each operator does complex arithmetic.

The imaginary unit i is just the constant {re: 0, im: 1}, and every function gets its principal-branch definition, so the identities you'd check by hand come out exactly right:

i²      = -1
√(-1)   = i
ln(-1)  = πi
e^(iπ)  = -1
Enter fullscreen mode Exit fullscreen mode

Each of those is what the live evaluator returns, to six decimals.

Speed matters, because the picture is built one pixel at a time. Timed in the browser, the complex evaluator runs about 3.4 million evaluations per second (200,000 evaluations of a rational function in 58 ms) on a single thread — enough to colour a full board and still respond to a drag.

Interactive speed without WebGL

There's no 3D here and no shader — the whole plane is drawn on a plain 2D canvas. The trick is resolution:

  • The plane is sampled into a half-resolution offscreen canvas (one sample per 2×2 block, ~77,000 for a full board) and scaled up with smoothing — close enough that the eye can't tell.
  • While dragging or zooming it drops to quarter resolution for a fluid feel, then repaints once at higher resolution 140 ms after you stop.
  • No animation loop runs when the image is still, so an idle plane costs nothing.

Because it's all arithmetic through a safe parser, a shared link can only ever draw a picture — ?cx= in the URL loads any f(z) straight onto the plane.

A field guide: reading functions by their fingerprint

Once you know the rule, each function has a signature you can read at a glance. Count how many times the colour wheel wraps a spot and you have the order of that zero or pole.

  • — a double zero. One dark point at the origin, but the hue wheel wraps it twice: squaring doubles every angle. Open it →
  • 1/z — a single pole. A bright point instead of a dark one, hue running the opposite way. Poles are zeros turned inside out. Open it →
  • (z² − 1)/(z² + 1) — zeros meet poles. Two dark zeros at ±1, two bright poles at ±i. Open it →
  • z³ − 1 — three roots of unity. Three dark zeros evenly spaced on the unit circle. Open it →
  • sin(z) — a row of zeros. Dark spots at every multiple of π, brightness climbing fast off the real axis. Open it →
  • eᶻ — no zeros, no poles. Horizontal bands of hue: the argument depends only on the imaginary part, brightness only on the real part. Open it →
  • ln(z) — a branch cut. One zero at z = 1 and a sharp seam along the negative real axis where the colour jumps. Domain coloring makes branch cuts visible. Open it →

Try it on your own function

Domain coloring turns "what does this function do near its zeros?" from a paragraph of algebra into a picture you read in a second. Open the graphing calculator, press f(z), and type something of your own — conj(z), z + 1/z, (z − i)/(z + i) — then hover to read z and f(z). A Colour intensity slider runs from pastel to vivid.

Full write-up with the maths: Domain Coloring: How We Plot Complex Functions in the Browser. Free, no sign-up, everything runs client-side.

Top comments (0)