Color Mapping¶
Color mapping is the process of mapping values in your data to colors in a plot. It allows you to increase the dimensionality of a graphic - for example, adding a third dimension to the two-dimensional x, y coordinates in a scatterplot. Good color mapping choices can reveal or emphasize patterns in your data while poor choices will obscure them, making color mapping an essential part of the Toyplot API.
If you haven’t already, see Colors for information on basic color management in Toyplot for creating and manipulating color values, color palettes, and color maps.
Mapping Data to Color¶
There are a few decisions to make whenever you map colors in Toyplot:
First, what is the cardinality of your data? Will you be specifying:
A constant color?
Per-series colors?
Per-datum colors?
Second, for per-series or per-datum colors, will you specify:
A custom palette?
Explicit colors?
Scalar values that are mapped to colors?
Third, if you choose to map scalars, will you provide:
Just scalar values?
Just a color map?
Both scalar values and a color map?
We will examine all of these options in the examples that follow. To begin, let’s create some random data series to use in our sample figures:
[1]:
import numpy
numpy.random.seed(1234)
samples = numpy.linspace(0, 4 * numpy.pi, 100)
frequency = lambda: numpy.random.normal()
phase = lambda: numpy.random.normal()
amplitude = lambda: numpy.random.uniform(0.1, 1)
wave = lambda: numpy.sin(phase() + (frequency() * samples))
signal = lambda: amplitude() * (2 + wave())
series = numpy.column_stack([signal() for i in range(10)])
single_series = series[:, 1]
Next, we’ll display the data without specifying any color information. Here is a plot with a single series:
[2]:
import toyplot
toyplot.fill(single_series, baseline="stacked", width=600, height=200);
And here’s the same plot with multiple series:
[3]:
toyplot.fill(series, baseline="stacked", width=600, height=300);
These examples allow us to see the answers to the questions posed above: by default, Toyplot generates per-series colors, using the default palette:
[4]:
toyplot.color.Palette()
[4]:
In the examples that follow, we will explore how to override these defaults.
Default Palettes¶
First, let’s specify an alternate palette to be used when assigning default per-series colors. The default color palette is a property of a coordinate system, so to change it we have to specify the palette when the coordinate system is created:
[5]:
palette = toyplot.color.brewer.palette("Set1")
palette
[5]:
[6]:
canvas = toyplot.Canvas(width=600, height=300)
axes = canvas.cartesian(palette=palette)
axes.fill(series, baseline="stacked");
Because the palette is a part of a coordinate system, it affects only those marks that you add to that system - if your plot contained multiple sets of axes, they can have any combination of default palettes that you like (although we always recommend that you find ways to use consistent color schemes in your plots!)
In the examples that follow, we will begin to override the colors provided by the axes palettes on a per-mark basis.
Constant Colors¶
Next, you can specify a single color value, which overrides the default palette and will be applied to all your data:
[7]:
toyplot.fill(single_series, color="steelblue", baseline="stacked", width=600, height=200);
[8]:
toyplot.fill(series, color="steelblue", baseline="stacked", width=600, height=300);
As you can see above, combining constant color and multiple series can obscure the series boundaries. In this case, you might use additional styling to make the boundaries visible again:
[9]:
style = {"stroke": "white"}
toyplot.fill(series, color="steelblue", style=style, baseline="stacked", width=600, height=300);
Of course, there are many ways that you could specify a constant color value. See Colors for details.
Per-Series Colors¶
More often than not, you will want to specify per-series colors to make the series in your data easy to differentiate - which is why Toyplot defaults to per-series colors. To specify per-series colors for any plot that contains \(N\) series, you can use any of the following with the color argument:
Alternate Color Maps¶
When you supply just a color map to the color argument, Toyplot applies it to a set of implicit color values in the range \([0, N)\) to generate colors for each of the \(N\) series in the plot. This can create duplicate colors if a categorical map doesn’t have enough colors in its palette, as in the following example:
[10]:
palette = toyplot.color.brewer.palette("BrownOrangeYellow", count=5)
colormap = toyplot.color.CategoricalMap(palette)
colormap
[10]:
[11]:
style = {"stroke" : toyplot.color.black, "stroke-width":0.5}
toyplot.fill(series, color=colormap, style=style, baseline="stacked", width=600, height=300);
A good way to avoid duplicates is to use a linear map instead of a categorical map - then, colors will be sampled across the full range of the palette without repetition, and assigned to each series:
[12]:
colormap = toyplot.color.brewer.map("BrownOrangeYellow")
colormap
[12]:
[13]:
toyplot.fill(series, color=colormap, style=style, baseline="stacked", width=600, height=300);
Scalar Values Plus Color Map¶
If you want more control over the mapping, you can replace the implicit \([0, N)\) values provided by Toyplot with your own range of \(N\) values to be mapped, passing a (values, colormap)
tuple to color
:
[14]:
values = numpy.linspace(0, 1, series.shape[1]) ** 0.5
toyplot.fill(series, color=(values, colormap), style=style, baseline="stacked", width=600, height=300);
Scalar Values Alone¶
As a special-case, if you supply just a set of \(N\) scalar values to color
, they will be mapped with a default diverging color map:
[15]:
toyplot.fill(series, color=values, baseline="stacked", width=600, height=300);
Explicit Colors¶
Finally, for complete control over color, you can bypass the mapping entirely, and provide a sequence of explicit color values, one per series. As an example, suppose you wanted to highlight one of the series in the following example:
[16]:
colormap = toyplot.color.brewer.map("BlueGreenBrown")
toyplot.fill(series, color=colormap, baseline="stacked", width=600, height=300);
Rather than manually create an explicit array of per-series colors from scratch, you can use :func:toyplot.color.broadcast
:
[17]:
colors = toyplot.color.broadcast(colormap, shape=series.shape[1])
colors
[17]:
The :func:toyplot.color.broadcast
function is used internally by Toyplot to implement the logic for mapping values to colors for varying data shapes, and returns a numpy array of RGBA colors when called. This allows us to generate the same set of colors as in the previous example. Then, we can manually modify the colors to suit our needs:
[18]:
colors[2] = toyplot.color.css("rgb(255, 235, 10)")
colors
[18]:
Finally, we pass the explicit list of colors to the color
argument when plotting the data:
[19]:
toyplot.fill(series, color=colors, baseline="stacked", width=600, height=300);
Of course, you are free to generate color data from scratch, as a numpy array of CSS color strings, or a list containing an arbitrary sequence of CSS color strings, RGB, and RGBA tuples. This is useful when you have an explicit color scheme dictated by your data and you don’t wish to create a Toyplot palette to match. For example:
[20]:
colors = ["crimson", "mediumseagreen", "royalblue"]
toyplot.fill(series[:,:3], color=colors, baseline="stacked", width=600, height=300);
Per-Datum Colors¶
For plot types that support it - such as bar plots and scatter plots - you can choose to map colors to individual datums instead of entire series. To enable per-datum color with a plot that takes an $M times N$ matrix containing $M$ datums and $N$ series, you simply provide an $M times N$ matrix of color values or scalars. As a special case, when your plot only contains a single series you can enable per-datum color by providing an array of $M$ color values or scalars.
Scalar Values Plus Color Map¶
As with per-series data, you can specify a set of per-datum scalars and a color map:
[21]:
toyplot.bars(single_series, color=(single_series, colormap), width=600, height=200);
[22]:
toyplot.bars(series, color=(series, colormap), baseline="stacked", width=600, height=300);