- Spectral
- RdYlGn
- RdYlBu
- RdGy
- RdBu
- PuOr
- PRGn
- PiYG
- BrBG
- Blues
- Greens
- Greys
- Oranges
- Purples
- Reds
- BuGn
- BuPu
- GnBu
- OrRd
- PuBuGn
- PuBu
- PuRd
- RdPu
- YlGnBu
- YlGn
- YlOrBr
- YlOrRd
- b2w_opaque
- b2w
- w2b_opaque
- w2b
Usage
I am assuming you follow the recommended use-case of simply having dropped
the heatmap.{c,h}
files into your project. If you are instead
linking to a precompiled library (which is likely the case if you are using
any of the higher-level language bindings), hopefully your provider
included all of the colorschemes in the library.
Simply drop the colorschemes/NAME_OF_SCHEME.{c,h}
files into your
project and you should be ready to go. The colorscheme is only used when
rendering the heatmaps, thus the only thing you have to change besides
#include
ing the colorscheme's header is your call to
heatmap_render_default_to
, which becomes:
#include <colorschemes/YlOrRd.h>
// ...
heatmap_render_to(hm, heatmap_cs_YlOrRd_mixed, &image[0]);
The name of the variable holding the colorscheme is a join of heatmap_cs
,
the colorscheme's name capitalized as-is, and the colorscheme's type
(discrete
, soft
, mixed
, mixed_exp
) using an underscore _
as glue
character. The header file to include is just the colorscheme's name, again
capitalized as-is.
It's as simple as that!
Creation of the Colorschemes
The remainder of this page explains how these colorschemes were generated.1 You might want to know this if you want to slightly change one of them, if you want to create a new one, or if you are just generally curious. We're going to use my go-colorful library, so install it:
><((("> go get github.com/lucasb-eyer/go-colorful
Handling of Gradients
All colorschemes are in fact just gradients. The soft
variants are plain
gradients, the discrete
variants are the gradients' keypoints, and the mixed
and mixed_exp
variants are an overlay of the former two.
We thus need to work with gradients. The only thing go-colorful
provides in
that line is blending two colors in various colorspaces using the Blend*
methods2. Now that's something we can build upon.
// This table contains the "keypoints" of the colorgradient you want to generate.
// The position of each keypoint has to live in the range [0,1]
type GradientKeypoint struct {
Col colorful.Color
Pos float64
}
type Gradient []GradientKeypoint
We just defined a gradient, (do read comments,) which we will use to generate
both the discrete and the soft colorschemes. The following two methods compute
the color of a gradient at any given position t
in between 0.0 and 1.0, using
go-colorful's BlendHcl
function.
// This is the meat of the gradient computation. It returns a HCL-blend between
// the two colors around `t`.
// Note: It relies heavily on the fact that the gradient keypoints are sorted.
func (self GradientTable) GetInterpolatedColorFor(t float64) colorful.Color {
for i := 0 ; i < len(self) - 1 ; i++ {
c1 := self[i]
c2 := self[i+1]
if c1.Pos <= t && t <= c2.Pos {
// We are in between c1 and c2. Go blend them!
t := (t - c1.Pos)/(c2.Pos - c1.Pos)
return c1.Col.BlendHcl(c2.Col, t).Clamped()
}
}
// Nothing found? Means we're at (or past) the last gradient keypoint.
return self[len(self)-1].Col
}
// This returns the color of the closest gradient keypoint.
// Note: This too relies on the fact that the gradient keypoints are sorted.
func (self GradientTable) GetColorFor(t float64) colorful.Color {
for i := 0 ; i < len(self) - 1 ; i++ {
if t < (self[i].Pos + self[i+1].Pos)*0.5 {
return self[i].Col
}
}
return self[len(self)-1].Col
}
With these two functions on our belt, we can create arbitrary gradients, by
simply generating an instance of Gradient
and calling
GetInterpolatedColorFor(t)
with t
walking from 0.0 to 1.0.
gradientgen.go
This is exactly what the gradientgen.go
tool does. Pass it a name, width,
height and a list of color-keypoint pairs and it will generate the colorscheme
defined by those:
><((("> go run gradientgen.go colorscheme.go -name awesome_greens \
'#F1F7EE' 0.0 '#D5E6CC' 0.1 '#B9D6AA' 0.2 '#9CC587' 0.3 '#80B465' 0.4 '#669B4B' 0.5 \
'#4F793A' 0.6 '#395729' 0.7 '#223419' 0.8 '#0C1208' 0.9 '#793A4F' 1.0
The above command will generate the awesome_greens.h
and awesome_greens.c
files which contain the four variations of the colorscheme defined by the above
keypoints. In addition, it generates four images, awesome_greens_discrete.png
,
awesome_greens_soft.png
, awesome_greens_mixed.png
, and
awesome_greens_mixed_exp.png
which show what the colorscheme looks like.
Anatomy of a Colorscheme
This last part explains how the colorscheme's .c/.h
files were generated. This
is done through a nifty use of an undocumented but intended feature of go's
templates.
In the header file, the only variable part is the colorscheme's name:
/* LICENSE BOILERPLATE */
#ifndef _HEATMAP_COLORSCHEMES_NAME_H
#define _HEATMAP_COLORSCHEMES_NAME_H
/* C-ABI guard */
extern const heatmap_colorscheme_t* heatmap_cs_name_discrete;
extern const heatmap_colorscheme_t* heatmap_cs_name_soft;
extern const heatmap_colorscheme_t* heatmap_cs_name_mixed;
extern const heatmap_colorscheme_t* heatmap_cs_name_mixed_exp;
/* C-ABI guard */
/* Include guard */
I'd hate to create a structure for a single-use containing a single variable or, even worse, two variables with the same content, all lowercase in one and all uppercase in the other. It'd just hurt.
Using go templates with just a single value is, although undocumented, actually very easy: use just the dot to reference the argument, as opposed to a member of it. While this is undocumented, it is being explicitly tested in exec_test.go around lines 250-265. Thus, the following will work as expected:
tmpl, err := template.New("test").Parse("Hello, {{.}}!")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, "World")
if err != nil { panic(err) }
Now that my first concern is addressed (no need of a struct), let's take a look
at the second one: ALL_CAPS. Go's templates provide us with a nifty feature,
highly inspired by unix's pipes, called filters. We can use a filter to
transform the data, for example turn it to uppercase. Unfortunately, we cannot
call string
's methods out of the box and thus need to let the template engine
know about them:
tmpl, err := template.New("test").Funcs(template.FuncMap{"ToUpper": strings.ToUpper}).Parse("Hello, {{ToUpper .}}!")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, "World")
if err != nil { panic(err) }
It's really that simple. The .c
file just contains a few more variables
containing the gradient data and thus makes use of a struct.
Footnotes
-
While almost all of these colorschemes originate from ColorBrewer, I have slightly modified some of them to look better in the form of a gradient and when used as a heatmap. ↩
-
go-colorful
doesn't provide more than color blending since anything that goes beyond this basic building-block is highly dependent on the use-case. Supporting all of those would blow the library out of proportions. ↩