Pssst! Rotate your device to landscape.

Or resize your window so it's more wide than tall.

Not supported for phones.

Domain Coloring for Visualizing Complex Functions with CindyJS

- Live Coding -


Hi! In this tutorial you will learn some basic elements for plotting complex functions using the method of domain coloring with CindyJS and the plugin CindyGL which is used for real-time colorplots. This tutorial does not require any prerequisites. However, some background in programming is always helpful.

Live coding means that you can type some CindyScript in the code-window on the right. If the code is valid, the applet will instantaneously process the input. You can also click on the big yellow code snippets within this tutorial to load the code to the live coding applet.

Now, before we start to plot complex functions we need to learn a little about the colorplot-command. If you are already familiar with this command, you can directly proceed to the section "Phase portraits" to start plotting complex functions.




The colorplot-command

In CindyJS the colorplot-command can be used to assign a color to each pixel (in the code-window on the right) with a given function.

Let us first start with an simple example:

colorplot(hue(seconds()/4));

Here every pixel gets a single color.

To make the color dependent on the position the pixel, you can access the pixel-coordinate via the variable #. # is a 2-component vector. Its first entry can be accessed with #_1 or #.x, its second with #_2 or #.y. An example that accesses the variables is the following:

colorplot(
  (1/2 + #.x/4, 1/2 + #.y/4, (sin(seconds()) + 1)/2)
);

With this, the red/green-component depends on the x/y-value of the pixel coordinate and the blue component varies with the time. All the three values have been rescaled such that they lie between $0$ and $1$.

Now, let us try to visualize the color wheel. For drawing a color wheel, we want to assign a color to every pixel visible of an annulus (ring).

So what do we need? First, we want to draw something within an annulus. Given a pixel coordinate #, we can check whether it lies within an annulus for example with 1<|#| & |#|<1.1. By using some branching with if we can compute different colors when we are inside our outside the annulus.

If we are inside the annulus, we want, given #, to calculate its angle to the origin and color the pixel according to this angle. The angle can be obtained by using the function arctan2(#). arctan2 is a convenient function, that is also available in various other computer languages, and returns the angle of a vector; Using something like arctan(#.y/#.x) instead would omit an important information on # because the sign sometimes cancels in the division. For example, $\frac{-1}{-1}=\frac{1}{1}$ and therefore the vectors (1,1) and (-1,-1) would yield the same value for arctan(#.y/#.x).

To feed the computed angle, which lies between $0$ and $2\cdot \pi$, to the hue-function, we have to rescale it to fit the whole color-domain by diving it by 2*pi.

Altogether, we obtain the following code:

colorplot(
  if(1 < |#| & |#| < 1.1,
    hue(arctan2(#)/(2*pi)),
    grey(0.4) //use grey outside the annulus
  )
);

Now let's plot some simple phase portraits of complex functions.




Phase portraits

A way to visualize complex functions $f:\mathbb{C}\to\mathbb{C}$ is using domain coloring to create phase portraits. A complex number can be assigned a color according to its phase/argument. Positive numbers are colored red; negative numbers are colored in cyan and numbers with a non-zero imaginary part are colored as in the following picture which shows the phase portrait for the function $f(z)=z$:

phase portrait for z

In his book Visual Complex Functions Elias Wegert employs phase portraits as an access to the theory of complex functions. For instance, roots and poles of a complex function $f$ can be easily spotted at the points where all colors meet. See for example the phase portrait of $f(z)=(z-1)/(z^2+z+1)$ in the following picture:

phase portrait

Generating phase portraits, like the two previous pictures, can be easily done on the GPU using the colorplot-command.

First let us define a complex function $f:\mathbb{C}\to\mathbb{C}$. A interesting function could be $f(z)=z^5-1$, which should have the five roots $\exp({k \tfrac{2 \pi i}{5}})$ for $k\in\{0,1,2,3,4\}$. We define $f$ in CindySript with f(z):=z^5-1.

CindyJS supports complex numbers and all its operations without additional implementations. One could evaluate f(i) and get $f(i)=i^5-1=i-1$:

f(z) := z^5 - 1;
drawtext((0,0), f(i));

Now we want to build a colorplot that interprets each pixel coordinate # as a complex number z, evaluates f(z) and colorizes the complex number f(z) according to the image above.

In order to interpret # as a complex number z we can either write z=#.x + i*#.y or use the equivalent function z=complex(#), which converts 2-component vectors of real numbers to a single complex number. Then we evaluate $f(z)$ and save it to the variable fz with fz=f(z). Now, in oder to colorize it, we will use the arctan2 function, which can also applied to complex numbers, to obtain the angle/phase of fz and plug the rescaled angle in the hue function.

The following code plots the simple phase portrait of $f(z)=z^5-1$ on the square $|\text{Re } z|<2$ and $|\text{Im } z|< 2$:

f(z) := z^5 - 1; //a complex function
colorplot(
    z = complex(#);
    fz = f(z);
    hue(arctan2(fz)/(2*pi))
);

Change the function f(z). Try for example:

  • f(z) := (z - 1)/(z^2 + z + 1)
  • f(z) := z^(1/2)
  • f(z) := log(z)
  • f(z) := z + 1/z
  • f(z) := z^(complex(mouse()))




Enhanced phase portraits

Level curves of phase and modulus

The previous visualization can be enhanced if lines of the same phase and modulus are highlighted. In this way we can appreciate the difference between roots and poles. For example, the function $f(z)=(z-1)/(z^2+z+1)$ has a root at $z_0=1$ and two poles at $$z_{1}=\frac{-1 + \sqrt{3}\,i}{2} \quad \text{and} \quad z_{2}=\frac{-1 - \sqrt{3}\,i}{2}.$$ These points can be easily spotted in the following image:

enhaced phase portrait

To generate the above image, an enhanced phase portrait, the following more complicated helper function colorize can be used:

f(z) := (z-1)/(z^2 + z + 1); //a complex function
colorize(z) := (
  n = 10; //highlight 10 different phases
  z = log(z)/(2*pi);
  zfract = n*z - floor(n*z); //n*z in C mod Z[i]
  factor = (0.6 + 0.5*re(sqrt(im(zfract)*re(zfract))));
  hue(im(z))*factor; //darken hue wrt factor
);
colorplot( colorize(f(complex(#))) );

The trick in designing colorize is to notice that the imaginary part of the logarithm of a complex number corresponds to the argument of the complex number, while the real part of the logarithm equals to the logarithm of the modulus of the complex number. By adding a grid over the values obtained by taking the logarithm, a grid that has iso-lines on phases and moduli is obtained.

To plot only iso-lines of phases then

factor = (0.6 + 0.5 * re(sqrt(im(zfract) * im(zfract)))).

On the other hand, if you want only iso-lines of moduli, then

factor = (0.6 + 0.5 * re(sqrt(re(zfract) * re(zfract)))).

You might have wondered why there is a re in re(sqrt(...)) in the code above? The problem is that CindyGL needs to be able to prove the types of all occurring variables and a colorplot should return a real color vector. If one takes the square-root of a real number, then the result, in general, is complex. To guarantee CindyGL that factor will always be real, we take the real part of the square-root.

Now it is maybe time to play around with other complex functions. For example, try the following functions:

  • f(z) := 1/z^5 - 1
  • f(z) := (z + 1)/(z - 1)
  • f(z) := sin(z)
  • f(z) := cos(1/(0.5 * z))
  • f(z) := z^(1 + 4 * cos(0.5 * seconds()) * i)
  • f(z) := log(z^(complex(mouse())))


Real and imaginary components

You can also plot the level curves of the real and imaginary components of the complex function. In this case you need the following code:

f(z) := z^2; //a complex function
colorize(z) := (
n = 4; //highlight different level curves
zc = log(z)/(2*pi); //coloring pixels
zl = 2*i*z/(2*pi); //level curves of components
zfract = n*zl - floor(n*zl); //n*z in C mod Z[i]
factor = (0.6 + 0.5*re(sqrt(im(zfract)*re(zfract))));
hue(im(zc))*factor; //darken hue wrt factor
);
colorplot( colorize(f(complex(#))) );




What is CindyJS?

CindyJS is a framework to create interactive (mathematical) content for the web. It provides the high-level mathematically oriented user with access to the shader language of the GPU without learning a shader language.

To learn more about this project visit Cindyjs.org.