.. _color:

Color

Color choices are critical in visualization because good color choices can reveal or emphasize patterns in your data while poor choices will obscure them.

Palettes

Much of the color management in Toyplot centers around :class:palette <toyplot.color.Palette> objects, which store ordered collections of RGBA colors. For example, consider Toyplot's default palette:


In [1]:
import toyplot.color
toyplot.color.Palette()


Out[1]:

Note: Like other Toyplot objects, palettes are automatically rendered in Jupyter notebooks, in this case as a collection of color swatches.

You will likely recognize the colors in the default palette, since they are used to provide default colors for series when adding marks to a plot. Although you can create your own custom palettes by passing a sequence of colors to the :class:toyplot.color.Palette constructor, we strongly recommend that you choose from the collection of high-quality palettes that are builtin to Toyplot, and outlined in the next section.

Color Brewer Palettes

Toyplot includes a complete collection of high-quality palettes from Color Brewer which are ideal for visualization (we will provide empirical evidence for this in the section on linear color maps) and accessed by name:


In [2]:
print toyplot.color.brewer.names()


['Accent', 'BlueGreen', 'BlueGreenBrown', 'BlueGreenYellow', 'BluePurple', 'BlueRed', 'BlueYellowRed', 'Blues', 'BrownOrangeYellow', 'Dark2', 'GrayRed', 'GreenBlue', 'GreenBluePurple', 'GreenYellow', 'GreenYellowRed', 'Greens', 'Greys', 'Oranges', 'Paired', 'Pastel1', 'Pastel2', 'PinkGreen', 'PurpleBlue', 'PurpleGreen', 'PurpleOrange', 'PurpleRed', 'Purples', 'RedOrange', 'RedOrangeYellow', 'RedPurple', 'Reds', 'Set1', 'Set2', 'Set3', 'Spectral']

In [3]:
toyplot.color.brewer("BlueYellowRed")


Out[3]:

You can also reverse the order of any palette (though this should almost never be necessary, as we will see shortly):


In [4]:
toyplot.color.brewer("BlueYellowRed", reverse=True)


Out[4]:

Each of the Color Brewer palettes comes in multiple variants with different numbers of colors. By default, when you create a Color Brewer palette, the one with the maximum number of colors is returned. However, you can query for all of the available variants, and request one with fewer colors if necessary:


In [5]:
toyplot.color.brewer.counts("BlueYellowRed")


Out[5]:
[3, 4, 5, 6, 7, 8, 9, 10, 11]

In [6]:
toyplot.color.brewer("BlueYellowRed", 5)


Out[6]:

Finally, each of the Color Brewer palettes are categorized as "sequential", "diverging", or "qualitative", which you can query by name:


In [7]:
toyplot.color.brewer.category("BlueYellowRed")


Out[7]:
'diverging'

Color Brewer Sequential Palettes

Sequential color palettes are designed to visualize magnitudes for some quantity of interest. Colors at one end of the palette are mapped to low values and colors at the opposite end map to high values. Toyplot includes the complete set of Color Brewer sequential palettes, reordered where necessary so that the colors always progress from low luminance to high luminance. This ensures that colormaps based on these palettes always map low values to low luminance and high values to high luminance (this is why you should never need to reverse a palette). The names of the palettes have been modified from the originals to eliminate abbreviations:


In [8]:
import IPython.display
for name in toyplot.color.brewer.names():
    if toyplot.color.brewer.category(name) == "sequential":
        IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
        IPython.display.display(toyplot.color.brewer(name))


BlueGreen
BlueGreenYellow
BluePurple
Blues
BrownOrangeYellow
GreenBlue
GreenBluePurple
GreenYellow
Greens
Greys
Oranges
PurpleBlue
PurpleRed
Purples
RedOrange
RedOrangeYellow
RedPurple
Reds

Color Brewer Diverging Palettes

Diverging palettes are especially useful when visualizing signed magnitudes, or magnitudes relative to some well-defined reference point, such as a mean, median, or domain-specific critical value. Once again, Toyplot includes the complete set of Color Brewer diverging palettes, reordered so the colors consistently progress from cooler colors to warmer colors, so low/negative values map to cool colors and high/positive values map to warm colors, and renamed for consistency:


In [9]:
for name in toyplot.color.brewer.names():
    if toyplot.color.brewer.category(name) == "diverging":
        IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
        IPython.display.display(toyplot.color.brewer(name))


BlueGreenBrown
BlueRed
BlueYellowRed
GrayRed
GreenYellowRed
PinkGreen
PurpleGreen
PurpleOrange
Spectral

Color Brewer Qualitative (Categorical) Palettes

Qualitative or categorical palettes are designed for visualizing unordered information. Adjacent colors typically have high contrast in hue or luminance, to emphasize boundaries between values. Toyplot includes the full set of qualitative palettes from Color Brewer, without modification:


In [10]:
for name in toyplot.color.brewer.names():
    if toyplot.color.brewer.category(name) == "qualitative":
        IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
        IPython.display.display(toyplot.color.brewer(name))


Accent
Dark2
Paired
Pastel1
Pastel2
Set1
Set2
Set3

You may recognize "Set2" as Toyplot's default color palette.

Linear Color Maps

While palettes group together related collections of colors, Toyplot uses color maps to perform the work of mapping data values to colors. The most important type of map in Toyplot is a :class:toyplot.color.LinearMap, which uses linear interpolation to map a continuous range of data values to a continuous range of colors, provided by a palette:


In [11]:
toyplot.color.LinearMap(toyplot.color.brewer("BlueYellowRed"))


Out[11]:

Note that not all linear interpolations of colors are equal! Because the human visual system is much more sensitive to changes in luminance than changes in hue, we typically want to use color maps that generate a linear range of luminance values. For example, the following figure relates the relationship between data values and luminance values for each of the Color Brewer sequential palettes:


In [12]:
import numpy
def luma_plot(colormaps):
    grid_n = 4.0
    grid_m = numpy.ceil(len(colormaps) / grid_n)
    canvas = toyplot.Canvas(grid_n * 150, grid_m * 150)
    for index, (name, colormap) in enumerate(colormaps):
        x = numpy.linspace(0, 1, 200)
        y = [toyplot.color.to_lab(color)[0] for color in colormap.colors(x)]

        axes = canvas.axes(grid=(grid_m, grid_n, index), ymin=0, ymax=100, gutter=20, xshow=True, yshow=True, label=name)
        axes.scatterplot(x, y, size=10**2, fill=(x, colormap))

In [13]:
luma_plot([(name, toyplot.color.LinearMap(toyplot.color.brewer(name), 0, 1)) for name in toyplot.color.brewer.names() if toyplot.color.brewer.category(name) == "sequential"])


0.00.51.0050100BlueGreen0.00.51.0050100BlueGreenYellow0.00.51.0050100BluePurple0.00.51.0050100Blues0.00.51.0050100BrownOrangeYellow0.00.51.0050100GreenBlue0.00.51.0050100GreenBluePurple0.00.51.0050100GreenYellow0.00.51.0050100Greens0.00.51.0050100Greys0.00.51.0050100Oranges0.00.51.0050100PurpleBlue0.00.51.0050100PurpleRed0.00.51.0050100Purples0.00.51.0050100RedOrange0.00.51.0050100RedOrangeYellow0.00.51.0050100RedPurple0.00.51.0050100Reds

And here are the diverging palettes, viewed in the same fashion:


In [14]:
luma_plot([(name, toyplot.color.LinearMap(toyplot.color.brewer(name), 0, 1)) for name in toyplot.color.brewer.names() if toyplot.color.brewer.category(name) == "diverging"])


0.00.51.0050100BlueGreenBrown0.00.51.0050100BlueRed0.00.51.0050100BlueYellowRed0.00.51.0050100GrayRed0.00.51.0050100GreenYellowRed0.00.51.0050100PinkGreen0.00.51.0050100PurpleGreen0.00.51.0050100PurpleOrange0.00.51.0050100Spectral

Note that for each of the palettes, the relationship between data and luminance is very close to linear, minimizing distortion, and that the diverging palettes provide a crisp transition between negative and positive values. When we say that Color Brewer is a "high quality" set of palettes, this is what we mean - the color choices in the Color Brewer palettes generate close-to-optimal relationships between data and luminance.

As a counterexample, here is the same analysis, applied to a low-quality colormap (mis)used by many mainstream visualization libraries:


In [15]:
luma_plot([("jet", toyplot.color.LinearMap(toyplot.color.Palette(numpy.load("jet.npy")), 0, 1))])


0.00.51.0050100jet

Note that this palette provides a complex luminance profile that makes it a poor choice for sequential or diverging data.

Moreland Diverging Maps

As an alternative to the linear maps based on the Color Brewer diverging palettes, Toyplot also provides a set of nonlinear diverging color maps based on “Diverging Color Maps for Scientific Visualization” by Ken Moreland at http://www.sandia.gov/~kmorel/documents/ColorMaps. You will note in the following plots that the Moreland diverging color maps use a much narrower range of available luminance - this is because they have been carefully crafted to provide a perceptually uniform mapping that takes both color and luminance into account to eliminate Mach banding effects:


In [16]:
luma_plot([(name, toyplot.color.diverging(name, 0, 1)) for name in toyplot.color.diverging.names()])


0.00.51.0050100BlueBrown0.00.51.0050100BlueRed0.00.51.0050100GreenRed0.00.51.0050100PurpleGreen0.00.51.0050100PurpleOrange

In [ ]: