Creating custom colormaps in matplotlib using cmap_builder
In this example, we will show how to use the cmap_builder
package to create complex colormaps and colorbars in matplotlib.
This tutorial covers the following topics:
Table of contents
But, first, let us begin plotting all the named colors available in matplotlib. A helper function is included cmap_builder.utils.plot_colortable
to quickly make this plot.
[1]:
%matplotlib inline
from cmap_builder.utils import plot_colortable
plot_colortable()
How to use cmap_builder?
Custom colormaps can be built using the cmap_builder.build_cmap()
function.
Let’s see the function’s help:
[2]:
from cmap_builder import build_cmap
help(build_cmap)
Help on function build_cmap in module cmap_builder:
build_cmap(name, cmap_def, discrete=False, uniform=False, N=512)
Build a colormap from a colormap definition (`cmap_def`) specifying the colors
at each point across the color scale.
The function returns a colormap, the data value indicating the boundaries of the
color segments, and a matplotlib's normalization function.
The colormap definition is a list where each entry represents the color at a given
data value as::
cmap_def = [
(x0, color_0, [next_color_0]) # next_color_0 ignored if provided.
(x1, color_1, [next_color_1])
...
(xi, color_i, [next_color_i])
...
(xn, color_n, [next_color_n]) # next_color_n is ignored if provided.
]
where `color_i` represents the color immediately before the `xi` the data value.
The optional `next_color_i` entry can be used to specify the color immediately after
the `xi` the data value. This allow creating color maps with sharp color transitions.
Any data interval is supported for the `xi`. For example, the same units as the data
to plot can be used.
Parameters
----------
name: str
Colormap name.
cmap_def: list
Colormap definition.
discrete: bool
If true, return a discrete colorbar.
Otherwise, a linear colormap is returned.
uniform: bool
If true, an uniform spacing is given to each color segment, regardless their
of their length. Otherwise, the original spacing specified in the
colormap definition `cmap_def` is used.
N: int
The number of rgb quantization levels for the colorbar.
512 by default (two times the matplotlib's defafult).
Returns
-------
cmap: ColorMap
A colormap that mapping data in the [0,1] interval to a given color.
x_values: list
The xi values in the colormap definition. This list can be used to place the
colorbar ticks at the boundaries of each color segment.
norm: Normalize
Normalization function that maps the data values [x0, ..., xi, ...,xn] into
the [0,1] interval. If `uniform` is true, the normalization with map the
data values into equally spaced color segments in the [0,1] interval.
Hence, in a nutshell, the first step to creating colormaps is specifying our colormap definition
cmap_def = [
(x0, color_0, [next_color_0]) # next_color_0 ignored if provided.
(x1, color_1, [next_color_1])
...
(xi, color_i, [next_color_i])
..
(xn, color_n, [next_color_n]) # next_color_n is ignored if provided.
]
Since this type of definition is very versatile, we will use it to create all sorts of colormaps.
Create non-uniform discrete colormaps
Now, let’s start building a simple colormap that has 5 discrete colors segments of different sizes.
We start first by defining the colormap as a (value, color) sequence, indicating the color at each data value. This is one of the supported colormap definitions supported by the cmap_builder
, but not the only one! (we will see other types of definitions later on).
Although the matplotlib’s colormaps map values in the (0,1) interval to colors, the cmap_builder
colormap definition supports using any units and intervals. The colormap normalization is done internally by the build_cmap
function. Defining the colormaps in the same data units allows one to easily create colormaps that fit particular datasets.
Without further ado, let’s create the colormap.
[3]:
# First, a helper function to visualize the colormap
def show_cmap(cmap, norm, xticks):
import numpy as np
from matplotlib import pyplot as plt
import matplotlib_inline.backend_inline
import numpy as np
matplotlib_inline.backend_inline.set_matplotlib_formats("svg")
plt.rc("font", family="serif", size=12)
gradient_1d = np.linspace(norm.vmin, norm.vmax, 700)
gradient = np.vstack((gradient_1d, gradient_1d))
figh = 1
fig, ax = plt.subplots(nrows=1, figsize=(6.4, figh), dpi=300)
fig.patch.set_facecolor("white") # Add white background to figure
fig.subplots_adjust(top=1 - 0.35 / figh, bottom=0.15 / figh, left=0.2, right=0.99)
ax.set_title(cmap.name, fontsize=14)
X, Y = np.meshgrid(gradient_1d, [0, 1])
ax.pcolormesh(X, Y, gradient, cmap=cmap, norm=norm, rasterized=True)
ax.set_yticks([])
ax.set_xticks(xticks)
Let’s first specify the colormap definition. Any named color supported by matplotlib can be used to define the colormap. IMPORTANT (r,g,b) values or hex colors are not supported yet.
[4]:
from cmap_builder import build_cmap
cmap_def = [
# (value, color)
(0, "red"),
(2, "blue"),
(4, "green"),
(8, "yellow"),
(9, "purple"),
(10, "purple"),
# The last repeated color is used to indicate the
# end of the discrete colormapping.
]
# Using the definition above, with `discrete=True`, we have a colormap that
# maps the following values to colors:
# [0-2) -> red
# [2-4) -> blue
# [4-8) -> green
# [8-9) -> yellow
# [9-10) -> purple
my_cmap, my_ticks, my_norm = build_cmap(
"non_uniform_discrete_cmap", # Name of the colormap
cmap_def,
discrete=True, # Return a discrete colormap.
N=700, # color palette quantization levels.
)
show_cmap(my_cmap, my_norm, my_ticks)
The build_cmap
function returns three objects: - cmap: The colormap - ticks: The data values corresponding to color segments definitions of the colormap. - norm: The norm used to normalize the data into the [0,1] interval used by the color plotting functions.
Let’s now use this colormap to create a simple plot.
[5]:
# Helper function to quickly make plots.
# We will use the same function for all the examples.
def make_plot(
cmap,
ticks,
norm,
title=None,
clabel="My colorbar",
xlabel="",
ylabel="",
return_axes=False,
):
# Make plots look pretty in the jupyter lab
import matplotlib.ticker as ticker
import matplotlib_inline.backend_inline
import numpy as np
from matplotlib import pyplot as plt
matplotlib_inline.backend_inline.set_matplotlib_formats("svg")
plt.rc("font", family="serif", size=12)
N = 1000
X, Y = np.mgrid[-2 : 2 : complex(0, N), -2 : 2 : complex(0, N)]
Z1 = np.exp(-(X ** 2) - Y ** 2)
Z = Z1 * 10
fig = plt.figure(figsize=(5, 4), dpi=300)
fig.patch.set_facecolor("white") # Add white background to figure
ax = plt.gca()
pcm = ax.pcolormesh(X, Y, Z, cmap=cmap, shading="auto", norm=norm, rasterized=True)
cb = fig.colorbar(pcm, ticks=ticks)
cb.set_label(clabel, labelpad=-2)
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
ax.set_title(title, pad=12)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_aspect("equal")
ax.axhline(0, linewidth=1.0, color="white", linestyle="--")
ax.axvline(0, linewidth=1.0, color="white", linestyle="--")
if return_axes:
return ax, cb
else:
plt.show()
[6]:
make_plot(my_cmap, my_ticks, norm=my_norm, title="Non-uniform discrete colormap")
Create non-uniform discrete colormaps with equally spaced color segments
Intencionally, we defined uneven color segments in the colormap. That does not look very nice in the colorbar.
To have equally spaced elements in the colorbar, we can recreate the colormap with the “uniform=True” option.
[7]:
my_cmap, my_ticks, my_norm = build_cmap(
"non_uniform_discrete_cmap", # Name of the colormap
cmap_def,
uniform=True, # Uniform spacing for each color segment
discrete=True, # Return a discrete colormap
N=700, # color palette quantization levels.
)
make_plot(
my_cmap,
my_ticks,
norm=my_norm,
title="Non-uniform discrete colormap\n(uniform spacing)",
)
Create linearly varying and non-uniform colormaps
Now, let’s use the colormap builder to create the same colormaps as before but using a continuous color transition. We use the same colormap definition as before, but this time we pass discrete=False
.
[8]:
cmap_def = [
# (value, color)
(0, "red"),
(2, "blue"),
(4, "green"),
(8, "yellow"),
(9, "purple"),
(10, "indigo"),
# The last repeated color is used to indicate the
# end of the discrete colormapping.
]
# Using the definition above, with `discrete=False`, we have a colormap that
# maps the following values to colors:
# [0-2) -> varies from red to blue
# [2-4) -> varies from blue to green
# [4-8) -> varies from green to yellow
# [8-9) -> varies from yellow to purple
# [9-10) -> varies from purple to indigo
my_cmap, my_ticks, my_norm = build_cmap(
"non_uniform_continuous_cmap", # Name of the colormap
cmap_def,
uniform=True, # Uniform spacing for each color segment
discrete=False, # Return a discrete colormap
N=700, # color palette quantization levels.
)
show_cmap(my_cmap, my_norm, my_ticks)
[9]:
make_plot(
my_cmap,
my_ticks,
norm=my_norm,
title="Non-uniform continous colormap\n(uniform spacing)",
)
Why use uniform spacing in continuous colorbars?
Using uniform spacing for the colormap segments gives additional control to the level of details of the plots. To better explain this point, let’s give a hypothetical meaning to the plots that we showed before. Let’s say we want to plot the “happiness” of a hummingbird as a function of the distance to the flower. Then, the x- and the y-axes denote the distance to the flower in meters, and the value of the function we plot is a measure of happiness. The happiness can be interpreted as the hummingbird being:
[0,2): Desperate!
[2, 4]: Worried.
[4-8]: A little worried.
[8-9]: Happy.
[9-10]: Extremely happy
Then, each of the above intervals has a particular (totally made up) meaning. Although any colormap (and their colorbar) can display the hummingbird mood as the function of distance, the boundaries where there is a transition of “happiness” are not clear. We can, of course, add contours to clearly denote the boundaries.
However, in the next section, we will see an alternative way to quickly see the type of mood of the little bird using more descriptive colormaps.
Create descriptive colormaps
The previous examples showed how the cmap_builder
library can create simple colormaps. Now, let’s make an awesome and very descriptive colorbar.
For that, we will create a colormap with the following properties:
Each different mood category is denoted by a different color.
Within each category, the values vary from light to dark colors according to the mood intensity.
Therefore, let’s build a colormap like this:
[0,2): Desperate!. Varying from dark to light green.
[2, 4]: Worried. Varying from dark to light purple.
[4-8]: A little worried. Varying from dark to light orange.
[8-9]: Happy. Varying from dark to light yellow.
[9-10]: Extremely happy. Varying from dark to light green.
Note that the mood intensity increases with decreasing values of our happiness measure of the happiness.
So, let’s begin by specifying the colormap definition.
[10]:
cmap_def = [
# (value, color)
(0, "red_dark"),
(2, "red_light", "orange_light"),
(4, "orange_dark", "blue_light"),
(8, "blue_dark", "purple_light"),
(9, "purple_dark", "green_light"),
(10, "green_dark"),
# The last repeated color is used to indicate the
# end of the discrete colormapping.
]
Did you note that we used named colors with the “_light” and “_dark” suffixes?
These are called “color modifiers” and it is one of the features included in the cmap_builder
library. Internally, the color names are parsed by the cmap_builder.utils.rgb_from_name()
function and supports any of the following color modifiers:
The color name supports at most one of the following color modifiers suffixes:
“_l#”: HLS lightness (L) modifier (0 to 100 range). For example, “yellow_l75” changes the lightness to 0.75.
“_s#”: HSV saturation (S) modifier (0 to 100 range). For example, “yellow_s75” changes the saturation to 0.75.
“_v#”: HSV value (V, or brightness) modifier (0 to 100 range). For example, “yellow_v75” changes the brightness (value) to 0.75.
“_light”: Make color lighter. Same as the “_l10” modifier.
“_dark”: Make color darker. Same as the “_l80” modifier.
Now, let’s build the colormap using the definition specified before.
[11]:
my_cmap, my_ticks, my_norm = build_cmap(
"hummingbird_happyness", # Name of the colormap
cmap_def,
uniform=True,
N=700, # color palette quantization levels.
)
show_cmap(my_cmap, my_norm, my_ticks)
[12]:
make_plot(
my_cmap,
my_ticks,
norm=my_norm,
title="Hummingbird happyness",
xlabel="x distance [m]",
ylabel="y distance[m]",
clabel="Hummingbird mood",
)
We can go one step further and replace the tick labels in the colorbar with different moods.
[13]:
ax, cbar = make_plot(
my_cmap,
my_ticks,
norm=my_norm,
title="Hummingbird happyness",
xlabel="x distance [m]",
ylabel="y distance[m]",
clabel="",
return_axes=True,
)
mid_point_ticks = [1, 3, 6, 8.5, 9.5]
mood_labels = ["Desperate!", "Worried", "A little worried", "Happy", "Extremely happy"]
_ = cbar.ax.set_yticks(mid_point_ticks)
_ = cbar.ax.set_yticklabels(mood_labels)
The previous plot describes the evolution of the little bird’s mood as it approaches a flower. We can see, for example, where the mood transition takes place and how the intensity of the mood changes with distance.