Colorschemes of libheatmap

You disabled JavaScript. Please enable it for syntax-highlighting, or don't complain about unlegible code snippets =) This page doesn't contain any tracking/analytics/ad code.

The locations of all 1773 deaths during the main event of DOTA2's TI3, comparable to a world championship.

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 #includeing 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 &lt; len(self) - 1 ; i++ {
        c1 := self[i]
        c2 := self[i+1]
        if c1.Pos &lt;= t &amp;&amp; t &lt;= 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 &lt; len(self) - 1 ; i++ {
        if t &lt; (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

  1. 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. 

  2. 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.