_images/toyplot.png

Colors

Color choices are critical in visualization because good color choices can reveal or emphasize patterns in your data while poor choices will obscure them. This section of the user guide will introduce Toyplot’s basic functionality for creating and manipulating colors. See Color Mapping for details on how to represent your data using color.

Color Values

Colors in Toyplot are represented as red-green-blue-alpha (RGBA) tuples, where each component can range from zero (off) to one (full strength). Alpha is used to represent the opacity of a color, from zero (completely transparent) to one (completely opaque). There are a variety of functions in Toyplot for creating color tuples using CSS strings, RGBA data, and even other color spaces:

[1]:
import toyplot.color
toyplot.color.rgb(1, 0, 0)
[1]:
[2]:
# Note: transparent red is *not* the same as opaque pink!
toyplot.color.rgba(1, 0, 0, 0.5)
[2]:
[3]:
toyplot.color.css("mediumseagreen")
[3]:
[4]:
toyplot.color.css("#248")
[4]:
[5]:
# CIE Lab color space:
toyplot.color.lab(75, 0, -100)
[5]:
[6]:
# Toyplot uses this as a default instead of pure black:
toyplot.color.black
[6]:
'#292724'

Color Palettes

In practice you should rarely need to manipulate individual color values as much of the color management in Toyplot centers around palette objects, which store ordered collections of colors. For example, consider Toyplot’s default palette:

[7]:
toyplot.color.Palette()
[7]:

You will likely recognize the colors in the default palette, since they are used to specify per-series colors when adding multiple data series to a figure. Although you can create your own custom palettes by passing a sequence of color values to the :class:toyplot.color.Palette constructor, we strongly recommend that you use the high-quality palettes from Color Brewer which are included with Toyplot and ideal for visualization. As we will see shortly, the Toyplot default palette is itself an example of a Color Brewer palette.

Each of the Color Brewer palettes is assigned to one of three categories, “sequential”, “diverging”, or “qualitative”, which we will discuss in-turn:

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, ordered so that colormaps based on these palettes always map low values to low luminance and high values to high luminance:

[8]:
import IPython.display
for name, palette in toyplot.color.brewer.palettes("sequential"):
    IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(palette)
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, ordered consistent so that low/negative values map to cooler colors and high/positive values map to warmer colors:

[9]:
for name, palette in toyplot.color.brewer.palettes("diverging"):
    IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(palette)
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:

[10]:
for name, palette in toyplot.color.brewer.palettes("qualitative"):
    IPython.display.display_html(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(palette)
Accent
Dark2
Paired
Pastel1
Pastel2
Set1
Set2
Set3

Usage

Color brewer palettes are created by name. For example, the following is the equivalent of Toyplot’s default palette:

[11]:
toyplot.color.brewer.palette("Set2")
[11]:

And here is a palette that is commonly used for diverging data:

[12]:
toyplot.color.brewer.palette("BlueRed")
[12]:

You can lookup the set of all available palette names:

[13]:
toyplot.color.brewer.names()
[13]:
['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']

Or you can lookup names for a specific category:

[14]:
toyplot.color.brewer.names("diverging")
[14]:
['BlueGreenBrown',
 'BlueRed',
 'BlueYellowRed',
 'GrayRed',
 'GreenYellowRed',
 'PinkGreen',
 'PurpleGreen',
 'PurpleOrange',
 'Spectral']

Alternatively, given a palette name, you can lookup its category:

[15]:
toyplot.color.brewer.category("BlueRed")
[15]:
'diverging'

When creating a palette, you can reverse the order of its color values (though this should almost never be necessary, thanks to the careful ordering of the palettes):

[16]:
toyplot.color.brewer.palette("BlueRed", reverse=True)
[16]:

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:

[17]:
toyplot.color.brewer.counts("BlueRed")
[17]:
[3, 4, 5, 6, 7, 8, 9, 10, 11]
[18]:
toyplot.color.brewer.palette("BlueRed", count=5)
[18]:

Palette Manipulation

Sometimes you’ll find yourself needing categorical palettes with more categories than the existing Color Brewer palettes provide. For these cases, you’ll have to create larger palettes on your own, but Toyplot can still help. First, the :func:toyplot.color.spread function can create a family of colors based on a single color value:

[19]:
toyplot.color.spread("mediumseagreen", lightness=0.9)
[19]:
[20]:
toyplot.color.spread(toyplot.color.rgb(1, 0.8, .05), lightness=0.2, count=6)
[20]:

Of course, this isn’t a complete solution, because you can only create so many colors of a single hue before they become indistinguishable. However, Toyplot makes it easy to concatenate palettes:

[21]:
toyplot.color.spread("steelblue", count=6) + toyplot.color.spread("crimson", count=5)
[21]:

Using this approach, you can, within reason, build-up arbitrarily large palettes. Often, the two-level hierarchy created by the combination of palette hues and lightness is useful for visually grouping families of related categories.

Color Maps

While palettes group together related collections of color values, Toyplot uses color maps to perform the real work of mapping data values to colors. Some color maps are based on the colors contained in a palette, while others calculate color values based on the subtleties of the human visual cortex; regardless, all color maps perform the basic function of mapping a collection of scalar values to a collection of colors.

Categorical Color Maps

The simplest type of color map in Toyplot is a toyplot.color.CategoricalMap, which uses scalar values as an index to lookup color values from a palette. As you might have guessed, if you create a default categorical map, it uses Toyplot’s default palette:

[22]:
toyplot.color.CategoricalMap()
[22]:

However, you can create a categorical map explicitly using any palette:

[23]:
toyplot.color.CategoricalMap(toyplot.color.brewer.palette("Set1"))
[23]:

As a convenience, :meth:toyplot.color.BrewerFactory.map can be used to create a map from any categorical Color Brewer palette:

[24]:
toyplot.color.brewer.map("Set1")
[24]:

Linear Color Maps

The most used type of color 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, specified using a palette. Unsurprisingly, Toyplot provides a default linear colormap, which is based on a diverging palette:

[25]:
toyplot.color.LinearMap()
[25]:

And as before, you can create linear maps either explicitly using palettes, or using the :meth:toyplot.color.BrewerFactory.map convenience function:

[26]:
toyplot.color.LinearMap(toyplot.color.brewer.palette("BlueRed"))
[26]:
[27]:
toyplot.color.brewer.map("BlueRed")
[27]:

Here are complete lists of the linear maps that can be created from the sequential and diverging Color Brewer palettes:

[28]:
for name, colormap in toyplot.color.brewer.maps("sequential"):
    IPython.display.display(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(colormap)
BlueGreen
BlueGreenYellow
BluePurple
Blues
BrownOrangeYellow
GreenBlue
GreenBluePurple
GreenYellow
Greens
Greys
Oranges
PurpleBlue
PurpleRed
Purples
RedOrange
RedOrangeYellow
RedPurple
Reds
[29]:
for name, colormap in toyplot.color.brewer.maps("diverging"):
    IPython.display.display(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(colormap)
BlueGreenBrown
BlueRed
BlueYellowRed
GrayRed
GreenYellowRed
PinkGreen
PurpleGreen
PurpleOrange
Spectral

Miscellaneous Linear Maps

In addition to linear maps based on the Color Brewer palettes, Toyplot provides a handful of additional high-quality color maps that can be created by name using :meth:toyplot.color.LinearFactory.map, for example:

[30]:
toyplot.color.linear.map("Blackbody")
[30]:

Here is the full list of additional linear maps:

[31]:
toyplot.color.linear.names()
[31]:
['Blackbody', 'ExtendedBlackbody', 'Kindlmann', 'ExtendedKindlmann']
[32]:
for name, colormap in toyplot.color.linear.maps():
    IPython.display.display(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(colormap)
Blackbody
ExtendedBlackbody
Kindlmann
ExtendedKindlmann

Moreland Diverging Maps

As an alternative to linear maps, 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. The Moreland maps are carefully crafted to provide a perceptually uniform mapping that takes both color and luminance into account to eliminate Mach banding effects. Unsurprisingly, they are created by name using :meth:toyplot.color.DivergingFactory.map:

[33]:
toyplot.color.diverging.map("PurpleOrange")
[33]:

And here are examples of the available maps:

[34]:
toyplot.color.diverging.names()
[34]:
['BlueBrown', 'BlueRed', 'GreenRed', 'PurpleGreen', 'PurpleOrange']
[35]:
for name, colormap in toyplot.color.diverging.maps():
    IPython.display.display(IPython.display.HTML("<b>%s</b>" % name))
    IPython.display.display(colormap)
BlueBrown
BlueRed
GreenRed
PurpleGreen
PurpleOrange

Mapping

To use a categorical colormap, you pass the scalar values to be mapped to the toyplot.color.CategoricalMap.colors() method:

[36]:
colormap = toyplot.color.brewer.map("Set1")
colormap
[36]:
[37]:
colormap.colors([0, 1, 4, 8])
[37]:

Although a palette contains a finite number of colors, a categorical map “wraps” out-of-range scalar values so that any scalar value can be mapped to a color, using repetition:

[38]:
colormap = toyplot.color.brewer.map("Set1", count=3)
colormap
[38]:
[39]:
colormap.colors([-1, 0, 1, 2, 3, 4, 5])
[39]:

Categorical maps are primarily designed to be used with integers, and floating point values are automatically truncated to integers during mapping:

[40]:
colormap.colors([0, 0.25, 0.75, 1, 1.25])
[40]:

Like categorical maps, linear color maps provide a :meth:toyplot.color.LinearMap.colors method that you use to perform the actual mapping:

[41]:
colormap = toyplot.color.brewer.map("BlueYellowRed")
colormap
[41]:
[42]:
colormap.colors([0, 1, 2])
[42]:

However, you may be surprised that the following yields identical results:

[43]:
colormap.colors([2, 3, 4])
[43]:

What happened? Unlike categorical maps, linear maps don’t have a “natural” mapping from scalar values to palette indices. Instead, they have a domain - a range of scalar values whose minimum and maximum are mapped to the first and last colors in the colormap, respectively. By default, every time you call toyplot.color.LinearMap.colors(), the domain is calculated from the data, so that - for this example - the smallest value will always be mapped to blue, and the largest value will always be mapped to red. In this way, the full range of colors is always used to display the full range of your data.

The default behavior is useful when you’re exploring your data for the first time, and don’t know its distribution. However, there are other cases where this behavior is incorrect: either because you have well-defined, problem-specific values that you want to map to specific colors, or because you want a color map to consistently assign the same color to the same data value, no matter the distribution of values with which it was called.

For these cases, you can explicitly specify the domain when you create the colormap. In the following examples, we map the domain to $[0, 4]$:

[44]:
colormap = toyplot.color.brewer.map("BlueYellowRed", domain_min=0, domain_max=4)

Now, with a consistent domain, the value “2” is consistently mapped to the same light-yellow color:

[45]:
colormap.colors([0, 1, 2])
[45]:
[46]:
colormap.colors([2, 3, 4])
[46]:

Note that, with an explicitly specified domain, it becomes possible to have values outside the domain, which will be mapped to the color of the nearest in-domain value:

[47]:
colormap.colors([-2, -1, 0, 4, 5, 6])
[47]:

Another special case to be aware of is what happens when the domain is empty, i.e. if you haven’t specified an explicit domain, and the minimum and maximum values in your data are the same. In this case, the mapping will return the first color in the colormap:

[48]:
colormap = toyplot.color.brewer.map("BlueYellowRed")
colormap.colors([0, 0, 0, 0])
[48]:

All of the above behaviors apply to the high quality linear and Moreland diverging maps, as well.

Color Maps and Perception

What do we mean when we speak of “high quality” color palettes and maps? While the choice of color palettes and interpolations may seem to be one of personal taste, not all color pairings are equal! The human visual system is much more sensitive to changes in luminance than changes in hue, so we typically want to use color maps that create linear changes in luminance where there are linear changes in the underlying data. To illustrate this point, the following figures will plot the relationship between data and luminance for all of the colormaps we’ve seen.

First, we will look at colormaps based on the Color Brewer sequential palettes:

[49]:
import docs
docs.plot_luma(toyplot.color.brewer.maps("sequential"))
[49]:
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

Note that for each of the maps, the relationship between data and luminance is close to linear (the ideal). Similarly, here are the other linear color maps provided by Toyplot, which provide nearly perfect luminance:

[50]:
docs.plot_luma(toyplot.color.linear.maps())
[50]:
0.00.51.0050100Blackbody0.00.51.0050100ExtendedBlackbody0.00.51.0050100Kindlmann0.00.51.0050100ExtendedKindlmann

Finally, here are the Color Brewer diverging colormaps which, once again, are close to linear, while providing a crisp transition between positive and negative slopes:

[51]:
docs.plot_luma(toyplot.color.brewer.maps("diverging"))
[51]:
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

In most situations, the preceding colormaps should be ideal. However, there are some circumstances where you will need to account for more than just luminance. In particular, some data may trigger an optical illusion called “Mach Bands” that can exaggerate contrasts in color. In this case, the Moreland diverging color maps may be a good choice:

[52]:
docs.plot_luma(toyplot.color.diverging.maps())
[52]:
0.00.51.0050100BlueBrown0.00.51.0050100BlueRed0.00.51.0050100GreenRed0.00.51.0050100PurpleGreen0.00.51.0050100PurpleOrange

Although the diverging colormaps use a much narrower range of available luminance that isn’t linear, they provide a perceptually uniform mapping that takes both color and luminance into account to eliminate Mach banding effects.

When we say that a palettes and colormaps in Toyplot are “high quality”, this is what we mean - they provide perceptually uniform relationships between data and color, so that viewers can intuitively relate changes in color to changes in magnitude for the underyling data.

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

[53]:
import numpy
jet = toyplot.color.Palette(numpy.load("jet.npy"))
docs.plot_luma("jet", jet)
[53]:
0.00.51.0050100jet

Note that this palette provides a complex, nonlinear luminance profile that makes it a poor choice no matter what type of data you wish to display, sequential or diverging!

Now that you are familiar with palettes and colormaps, see Color Mapping to see how they are used to display data in a plot.