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:

  • 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. Let’s begin by creating several data series for use in our examples:

import numpy

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:

import toyplot
toyplot.fill(single_series, baseline="stacked", width=600, height=200);

And here is the same type of plot with multiple series:

toyplot.fill(series, baseline="stacked", width=600, height=300);

These examples allow us to see Toyplot’s default answers to the questions asked above: it generates per-series colors, using a categorical color map based on the default palette. With this in mind, we can begin to override the defaults in the following sections.

Constant Colors

First - and simplest - you can simply specify a single color value, which will be applied to all your data:

toyplot.fill(single_series, color="steelblue", baseline="stacked", width=600, height=200);
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:

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:

palette = toyplot.color.brewer.palette("BrownOrangeYellow", count=5)
colormap = toyplot.color.CategoricalMap(palette)
style = {"stroke" : toyplot.color.near_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:

colormap = toyplot.color.brewer.map("BrownOrangeYellow")
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:

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:

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:

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 toyplot.color.broadcast():

colors = toyplot.color.broadcast(colormap, shape=series.shape[1])

The 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:

colors[2] = toyplot.color.css("rgb(255, 235, 10)")

Finally, we pass the explicit list of colors to the color argument when plotting the data:

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:

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:

toyplot.bars(single_series, color=(single_series, colormap), width=600, height=200);
toyplot.bars(series, color=(series, colormap), baseline="stacked", width=600, height=300);