Home About Email Resume Links

26 Dec 2017
Plotting 3D Vectors Using HSL in Python

Thanks to advocacy on the part of data visualization and color experts, more and more people are using perceptually uniform color schemes. Also, modern plotting and visualization tools are starting to adopt uniform colormaps as their defaults. There are plenty of better places to learn details about why perecptualy uniformity is desirable, but the short answer is that nonuniformities impose artifacts on your data. The gradients in your data can be exaggerated or masked by the choice of colormap. The difference is obvious when you look at uniform and nonuniform maps next to eachother, especially when they are converted to grayscale, which is a measure of lightness.

Recently I wanted to plot a vector field (magnetization of a lattice) as an image by creating a mapping between a vector and a color, a common technique. Typically a cylindrical color coordinate like Hue-Saturation-Lightness is used. Each pixel in the image represents a vector. The color of the pixel is determined by the components of the vector: the hue is determined by the azimuthal angle, the saturation by the length of the vector, and the lightness by the z-component. In each pixel of the example image below the saturation is unity and the z-component is 0. This is a plot of arctan(y/x) so only the azimuth is changing.

Unfortunately, the HSL scheme, as implemented in most visualization software, is not uniform. Notice the radial streaks above. The state of the art method for solving this problem, as far as I can tell (I’m a scientist not a dataviz expert!), is HSLuv. The designers of this scheme formed a special subregion of the HSL space designed to be uniform, and then released implementations for many major programing languages.

Below are some examples using Python.


In matplotlib, HSL values and RGB values are all 3-vectors with values in [0, 1].

# Convert to cylindrical
x, y, z = [cartesian_data[:,:,i] for i in range(3)]
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
# Change domain from [-pi, pi] to [0, 2pi]
phi[phi < 0] += 2 * np.pi
# Normalize so that all are in [0, 1]
r, phi, z = [normalize(i) for i in (r, phi, z)]

# Plot with matplotlib, conversion func available from colorsys
# Install with pip install colorsys
import matplotlib.pyplot as plt
from colorsys import hls_to_rgb
# Unfortunately this function doesn't work with numpy.ndarray
rgb = [hls_to_rgb(phi[i,j], z,[i,j] r[i,j]) for j in range(N)] for i in range(N)]


In HSLuv the input values to the conversion function have different ranges. Hue is in [0, 360], and saturation and lightness are in [0, 100]. HSLuv is pip installable with pip install hsluv. Notice that the colorsys and hsluv functions have slightly different call signatures. I am actually going to use the hpluv variety of this color scheme becuase it is the fully uniform version. See the documentation for the differences between hsluv, hpluv and other similar schemes.

phi *= 360.0
r  *= 100.0
z *= 100.0

from hsluv import hpluv_to_rgb
rgb = [hpluv_to_rgb(phi[i,j], z,[i,j] r[i,j]) for j in range(N)] for i in range(N)]


Home About Email Resume Links