_images/toyplot.png

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);
0501000.00.51.01.52.02.5

And here’s the same plot with multiple series:

[3]:
toyplot.fill(series, baseline="stacked", width=600, height=300);
05010004812

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");
05010004812

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);
0501000.00.51.01.52.02.5
[8]:
toyplot.fill(series, color="steelblue", baseline="stacked", width=600, height=300);
05010004812

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);
05010004812

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);
05010004812

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);
05010004812

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);
05010004812

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);
05010004812

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);
05010004812

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);
05010004812

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);
05010001234

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);
0501000.00.51.01.52.02.5
[22]:
toyplot.bars(series, color=(series, colormap), baseline="stacked", width=600, height=300);
05010004812

Scalar Values Alone

And as before, you can supply just the per-datum scalars, and they will be mapped using a default diverging color map:

[23]:
toyplot.bars(single_series, color=single_series, width=600, height=200);
0501000.00.51.01.52.02.5
[24]:
toyplot.bars(series, color=series, baseline="stacked", width=600, height=300);
05010004812

Explicit Colors

Finally, you can still supply explicit per-datum color values using :func:toyplot.color.broadcast. In the following example we will use it to highlight the ten largest values in the data:

[25]:
colors = toyplot.color.broadcast((single_series, colormap), shape=single_series.shape)

order = numpy.argsort(single_series, axis=None)
colors.flat[order[-10:]] = toyplot.color.css("yellow")

toyplot.bars(single_series, color=colors, baseline="stacked", width=600, height=200);
0501000.00.51.01.52.02.5
[26]:
colors = toyplot.color.broadcast((series, colormap), shape=series.shape)

order = numpy.argsort(series, axis=None)
colors.flat[order[-10:]] = toyplot.color.css("yellow")

toyplot.bars(series, color=colors, baseline="stacked", width=600, height=300);
05010004812

Color Arguments

In the preceeding examples we used the color argument to supply color mapping data for various types of visualization. Keep in mind that many plot types provide more than one color argument, depending on the structure of the plot. For example, you can use color mapping to control the per-series color of a set of line plots:

[27]:
colormap = toyplot.color.brewer.map("Dark2")
toyplot.plot(series[::3,:2], color=colormap, width=600, height=200);
01020300.00.51.01.52.02.5

… but you can also apply color mapping to per-datum markers in the plot:

[28]:
toyplot.plot(series[::3,:2], color=colormap, mfill=series[::3,:2], marker="o", size=8, width=600, height=200);
01020300.00.51.01.52.02.5

See the reference documentation for the individual plot types for information on which arguments accept color mapping parameters.