Interactive H$_{CL}$ Colormap Designer

This notebook is based off a proposed system for designing the new default matplotlib colormap proposed here. It generalizes the approach into an interactive IPython widget, so that one can play with the ramps for the three paramters and see in real time what the resulting colormap will look like.

Originally written by Michael Waskom, February 18, 2015. Placed in public domain.

In [1]:
%matplotlib inline
In [2]:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from IPython.html.widgets import interact, IntRangeSlider, IntSlider
:0: FutureWarning: IPython widgets are experimental and may change in the future.
In [3]:
from colormath.color_objects import sRGBColor, LCHabColor, LabColor
from colormath.color_conversions import convert_color
In [4]:
def in_gamut(rgb_color):
    rgb_vals = np.array(rgb_color.get_value_tuple())
    return ((0 < rgb_vals) & (rgb_vals < 1)).all()
In [5]:
def make_colormap(L_ramp, c_ramp, h_ramp):
    rgb_colors = []
    gamut_status = []
    for L, c, h in zip(L_ramp, c_ramp, h_ramp):
        Lch = LCHabColor(L, c, h)
        rgb = convert_color(Lch, sRGBColor)
        rgb_colors.append((rgb.clamped_rgb_r, rgb.clamped_rgb_g, rgb.clamped_rgb_b))
        gamut_status.append(in_gamut(rgb))
    cmap = mpl.colors.LinearSegmentedColormap.from_list("hcl_colormap", rgb_colors)
    return cmap, gamut_status
In [6]:
def plot_colormap(cmap, gamut_status):
    f, (ax_cmap, ax_gamut) = plt.subplots(2, figsize=(10, 1), sharex=True,
                                          gridspec_kw={"height_ratios": (3, 1)})
    x = np.linspace(0, 1, 256)
    ax_cmap.pcolormesh(np.array([x]), cmap=cmap)
    ax_cmap.set_axis_off()

    ax_gamut.pcolormesh(np.array(gamut_status)[np.newaxis, :], cmap="Greys_r", vmin=-.5)
    ax_gamut.set_axis_off()
    ax_gamut.set(xlim=(0, 256))

Here's the widget itself. If you downloaded this notebook, run the cell and you'll see the interactive part. It shows the colormap and then below the colormap it shows where we have strayed out of gamut (assuming I did the calculations right).

In [7]:
@interact
def choose_colormap(
        L=IntRangeSlider(min=0, max=100, value=(20, 90), width=450),
        c=IntRangeSlider(min=0, max=100, value=(30, 50), width=450),
        h_start=IntSlider(min=0, max=360, value=240, width=450),
        h_rot=IntSlider(min=-360, max=360, value=210, width=450)
        ):
    
    L_ramp = np.linspace(*L, num=256)
    c_ramp = np.linspace(*c, num=256)
    h_ramp = np.linspace(h_start, h_start + h_rot, 256) % 360
    cmap, gamut_status = make_colormap(L_ramp, c_ramp, h_ramp)
    plot_colormap(cmap, gamut_status)
In [ ]: