temperature-map-gl.js – Minimalist pure Javascript Heat Map Library using WebGL shaders

Minimalist Library to draw temperature maps (heat maps) using WebGL in pure Javascript. Except a O(N) pre-process step which is done in Javascipt, all calculations and drawing are done with shaders in WebGL, so it is pretty fast. It is also very small (~3kB minified and gzipped)

‘OES_texture_float’ extension is required.

Using the library

HTML

...
<script type='text/javascript' src='temperature-map-gl.min.js'></script>
...
<div class='map-container' style='position:relative;'>
    <img id='map-image0' src='symi.png'/>
</div>

Javascript

var image = document.getElementById("map-image0");
var temperature_map = new temperature_map_gl(image);
temperature_map.set_points(points);
temperature_map.draw();

points are in this format:

var points = [
    [x0,y0,v0],
    [x1,y1,v1],
    ...
    [xN,yN,vN]
]

Available options (the defaults are shown)

var temperature_map = new temperature_map_gl(image), {
    p: 3, // used in calculating the IDW values, see wikipedia article mentioned at the bottom of this
    canvas: null, //use this canvas element and don't create a new one
    opacity: 0.5,// opacity of the canvas
    range_factor: 0.01,//used in scaling the values so they don't clip when storing them as channels of the framebuffer texture
    gamma: 2.2,//used in altering the color during draw pass
    brightness: 0.00,//used in brightening the color during draw pass
    contrast: 6,//used in altering the color during draw pass
    show_points: false,//add 
    framebuffer_factor: 0.25,//the ratio of the dimensions of the calculation framebuffer in relation to the actual canvas
    image_zindex: 0,//style z-index given to the image
    floating_point_texture: true,//use floating point texture for calculation, if not supported this automatically becomes false, but the results are incorrect...
    point_text: function(val) {//used when the show_points is true of the draw_points() method is called explicitly. It returns the text on the points shown for given value val
        var v;
        if(val < 1)
            v = val.toFixed(2);
        else if(val < 10)
            v = val.toFixed(1);
        else
            v = Math.round(val);
        return v + "°C";
    }
};

Methods

//constructor
temperature_map_gl(image_element[, options]);

//update some options, not all constructor options will have an effect..
temperature_map.update_options(options);

//sets points in the format mentioned above
temperature_map.set_points(points);

//performs a calculation and draw given the points set
temperature_map.draw();

//explicitly draw markers on points, like using the show_points option
temperature_map.draw_points()

//explicitly hide markers
temperature_map.hide_points()

//removes all created elements
temperature_map.destroy()

Examples

You can also check it out live at chpetrou.net

Technical explanation

Values are calculated using ‘Inverse Distance Weighting (IDW)’ algorithm:

Wikipedia – Inverse Distance Weighting

The rest of the explanation makes sense only in the context of the wikipedia article above…

For every point, we perform a render pass to a texture. Using IDW, we calculate the point “influence” to every fragment using a fragment shader. We store the ui*wi at the r channel of the texture and w_i at the g channel. Using blending with “accumulator” configuration, we end end up with a texture, where we have the top sum of IDW in r channel, and the bottom sum at the g channel. Since channels WebGL are clamped in [0,1], we multiply both channels with range_factor to avoid clamping.

At last, we perform a last pass where we get the IDW value by reading the calculation texture and do a r/g at every fragment. We then use this value to determine the color of the fragment.

Why?

I was looking for js libraries for implementing temperature maps for floorplans for HAM. There are not a lot of libraries that fit my requirements, except for this library. The problem with this library it is that calculations are done in CPU, so it is SLOW, there’s got be a better way! However, it pointed me to the right direction as for computing the values in the fragments, the next step was how can I take advantage of the GPU.

At first I thought that I could just pass the points as an uniform array. However, this seemed like a naive approach and GPUs are not doing well with the branching this approach would entail. After some thought, I ended up with the approach I ended up using.

You can download it from github