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