Domain coloring for complex functions.
- What are portraits?
- Phase colors and colormaps
- Installing the ComplexPortraits package
- Using the package
- Creating, viewing and saving portraits
- PlotRecipe support
- Examples of color schemes
To visualize a function f: ℂ → ℂ these steps are done:
- Coloring of the codomain/target set: every w ∊ ℂ gets a color, lets call it C(w).
- To get the color at z ∊ ℂ (where f is defined) one calculates w = f(z) and
- the color at z is computed using the map C∘f, i.e. the color at the point z is given by the C(f(z)).
Here is an example for f(z) = z².
For more flexibility in this package the color at z may also depend on z itself: color at z = CS(z, f(z)). Such a function is called color scheme.
This process is called domain coloring. There are many different ways to color the w-plane. Often the color is primarily determined by the angle/phase of w. In such a situation this is also called a (complex) phase portrait. A lot of properties of f can be directly seen in or deduced from such a phase portrait.
For a lot more details, see the book "Visual Complex Functions" by Elias Wegert, Birkhäuser, 2012.
Some of the ideas of Elias Wegert's PhasePlot package/Complex Function Explorer for Matlab are implemented here for Julia. Especially the following one-lettter-colorschemes are also available in ComplexPortraits: c, d, e, j, m, p, q. Hence a copy of the BSD-licensed Complex Funktion Explorer source code can be found in the based_on directory.
Additionally some other color schemes are implemented. Please see the list of examples below.
The basis for many color schemes are the colormaps for the angles/phases. There exist two typical choices (color wheels):
HSV wheel: and NIST wheel:
Of course you can define and use your own colormap.
using Pkg
Pkg.add(PackageSpec(url="https://github.com/luchr/ComplexPortraits.jl", rev="master"))
To save diskspace all images are not included in this Julia-Package, but are saved in ComplexPortraitsImages.
By default this package does not import any names in the global namespace (no namespace pollution). There are two macros if you want to import some names (import_normal
and import_huge
):
using ComplexPortraits
@ComplexPortraits.import_normal
# or @ComplexPortraits.import_huge
The main function is portrait
:
function portrait(z_upperleft, z_lowerright, f;
no_pixels=(600,600), point_color=cs_j())
Two complex numbers are needed: the upper left and lower right corner of the rectangular domain which should be colored. f
is the complex function.
The other arguments are optional: With no_pixels
the size of the output image (number of pixels) can be given. point_color
is a color scheme function. A color scheme function has the form
mycolorscheme(z, fz) = ... # computation of color depending on z and f(z)
It takes two scalar complex numbers (typically of type Complex{Float64}
) and returns a color, see ColorTypes.
Please see below for a list of predefined parameterized color schemes. All cs_foobar(baz)
functions in the package are function generators, i.e. they use the given parameters
to generate and return a color scheme function. For example:
cs_e(phaseres=10)
uses the phaseres
argument to create and return a color scheme
function with the given phase-resolution.
The output of portrait
is a matrix of color entries (a.k.a. image).
There are many ways to save such an image, e.g. using FileIO and ImageMagick:
using ComplexPortraits
using ImageMagick
using FileIO
img = portrait(-2.0 + 2.0im, 2.0 - 2.0im, z -> z^2)
save(File(format"PNG", "z_squared.png"), img)
To directly view such an image in Jupyter, Atom, etc. (or in an extra window) and/or process the image in other ways, please see and use the JuliaImages ecosystem.
Since version 0.2.0 Plots.jl
is supported via Plot recipes:
using Plots, ComplexPortraits
ComplexPortraits.phaseplot(-2.0 + 2.0im, 2.0 - 2.0im, z -> exp(z) - z)
Here is the full list if kwargs for phaseplot
:
phaseplot(z_upperleft, z_lowerright, f;
no_pixels=(600, 600),
point_color=cs_j(),
no_ticks=(7, 7),
ticks_sigdigits=2)
For all the examples the function z -> (z-1)/(z*z + z + 1)
is used. It
has two simple poles, one root and one saddle point.
For the color schemes in this section the 1-periodic "spike function"
x -> 1 - exp(a*abs(mod(x,1)-0.5)^b)
with two parameters a and b is
often used as a factor to turn something off (at 0.5 + ℤ):
cs_gridReIm(; reim_a=-9.0, reim_b=0.5)
Use Nist-Colors for the phase and use the spike function to decrease the brightness periodically on a real-imag grid.
reim_a=-9.0, reim_b=0.5
:
reim_a=-9.0, reim_b=0.8
:
cs_gridReIm_logabs(; reim_a=-9.0, reim_b=0.5, abs_a=-9.0, abs_b=0.8, vcorr=(s,v) -> max(1.0 - s, v))
Additional to cs_gridReIm
use another spike function to
decrease the saturation depending on the log(abs(w)).
An additional function vcorr
can be used to adjust the
brightness depending on the saturation to control whether the black lines
are "in front of the white lines" or the white lines are in front.
reim_a=-9.0, reim_b=0.5, abs_a=-9.0, abs_b=0.8
:
reim_a=-9.0, reim_b=0.8, abs_a=-9.0, abs_b=0.9
:
cs_gridReIm_abs(; reim_a=-9.0, reim_b=0.5, abs_a=-9.0, abs_b=0.8, vcorr=(s,v) -> max(1.0 - s, v))
Like cs_gridReIm_logabs
, but the low saturation lines (white lines)
depend on abs(w).
reim_a=-9.0, reim_b=0.5, abs_a=-9.0, abs_b=0.8
:
The following color schemes all use a colormap for the phase, i.e. the phase
is discretized with the colors of the colormap
-argument.
Use hsv_colors(color_number=600; saturation=1.0, value=1.0)
for the HSV-colormap and nist_colors(color_number=600; saturation=1.0, value=1.0)
for the NIST-colormap.
cs_p(; colormap=hsv_colors())
A vanilla phase portrait ("proper").
cs_p():
cs_j(; colormap=hsv_colors(), jumps=collect(LinRange(-π, π, 21))[1:20], a=0.8, b=1.0)
This is a cs_p
with specific phase jumps.
cs_j()
:
cs_j(jumps=[60/180*pi, 240/180*pi])
:
cs_d(; colormap=hsv_colors())
This is a cs_p
with the brightness adapted to the log(abs(fz)).
cs_d()
:
cs_q(; colormap=hsv_colors(20)) = cs_p(; colormap=colormap)
Same as cs_p
but with a colormap with only 20 entries.
cs_q()
:
`cs_m(; colormap=hsv_colors(), logabsres=20)´
Similar to cs_p
with additional brightness jumps depending on log(abs(fz)).
cs_m()
:
cs_m(phaseres=10)
:
cs_e(; colormap=hsv_colors(), logabsres=20)
This is a cs_m
with brightness changed w.r.t. abs(fz) [near zero and infinity].
cs_e()
:
cs_e(logabsres=10)
:
cs_c(; colormap=hsv_colors(), phaseres=20)
Phase plot with conformal polar grid.
cs_c()
:
cs_e(logabsres=10)
:
cs_useImage(image; img_upperleft=0.0 + 1.0im, img_lowerright=1.0 + 0.0im, is_hv_periodic=(true, true))
Here an given image
is placed in the target complex plane in the rectangle given by img_upperleft
and img_lowerright
. Optionally this image can be repeated horinzontal and/or vertical. The colors of the (pixels of the) image are used to colorize portrait.
For this example the following image (test_img
) is used:
cs_useImage(test_img; img_upperleft=0.0+0.2im, img_lowerright=0.8+0.0im, is_hv_periodic=(false, true))
cs_useImage(test_img; img_upperleft=0.0+0.2im, img_lowerright=0.8+0.0im, is_hv_periodic=(true, true))
cs_stripes(directions, colors)
Directions is a vector of complex numbers. For each such number d in directions the stripes are perpendicular to (real(d), imag(d)). And abs(d) is the number of stripes per unit length. This devides the complex plane in two sets Sd and ℂ\Sd.
Each z ∊ ℂ has n=length(directions) flags (z ∊ S1, z ∊ S1, ..., z ∊ Sn). This make 2^n possible flag-combinations. For every combination there need to be a color in colors.
cs_stripes([5.0 + 0.0im], [HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0)]))
cs_stripes(
[5.0 + 0.0im, 0.0 + 5.0im],
[HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0),
HSV(0.0, 1.0, 1.0), HSV(240.0, 1.0, 1.0)])
cs_stripes(
exp(-1.0im*pi/4).*[2.0 + 0.0im, 0.0 + 2.0im, 2.0 + 2.0im],
[HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0),
HSV(0.0, 1.0, 1.0), HSV(60.0, 1.0, 1.0),
HSV(120.0, 1.0, 1.0), HSV(180.0, 1.0, 1.0),
HSV(240.0, 1.0, 1.0), HSV(300.0, 1.0, 1.0)])
cs_angle_abs_stripes(directions, colors)
Uses cs_stripes
for (angle(z), abs(z)).
cs_angle_abs_stripes([5.0 + 0.0im], [HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0)])
cs_angle_abs_stripes(
[5.0 + 0.0im, 0.0 + 5.0im],
[HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0),
HSV(0.0, 1.0, 1.0), HSV(240.0, 1.0, 1.0)])
cs_angle_abs_stripes(
exp(-1.0im*pi/4).*[2.0 + 0.0im, 0.0 + 2.0im, 2.0 + 2.0im],
[HSV(0.0, 0.0, 1.0), HSV(0.0, 0.0, 0.0),
HSV(0.0, 1.0, 1.0), HSV(60.0, 1.0, 1.0),
HSV(120.0, 1.0, 1.0), HSV(180.0, 1.0, 1.0),
HSV(240.0, 1.0, 1.0), HSV(300.0, 1.0, 1.0)]))