Streets Ahead

Presents

Interesting Stuff

Dynamically Generate Histogram Images with Node.js and Canvas

As part of a new iOS app I've been working on I had the need to dynamically generate some images to represent some data. I have played a little with HTML5 canvas before, and I stumbled upon this project a while back so I decided it was time to give it a try to see if it would fullfill my need. In the end it ended up working out really well, I was able to throw the following proof of concept together in about 20 minutes (after about an hour of fighting through the install, there are a few pain points in Mountain Lion).

Here is the code used to render my histogram

function render(canvas, numbers, color) { 
    ctx = canvas.getContext('2d');

    var biggestNumber = Math.max.apply(null, numbers);
    var currentX = 10 + WIDTH/2;
     var lineHeight = 0;

    ctx.lineWidth = WIDTH;
    ctx.strokeStyle = color;

    ctx.beginPath();
    ctx.moveTo(currentX, HEIGHT + 10);
    numbers.forEach(function(it, ind) {
        lineHeight = (HEIGHT+10) - (HEIGHT * (it / biggestNumber));
        ctx.lineTo(currentX, lineHeight);
        currentX += WIDTH;
        ctx.moveTo(currentX, HEIGHT + 10);
    });
    ctx.closePath();
    ctx.stroke();
}

So, you can see its really simple code, I just get a graphics context, then initialize some variables. Then I just create a path containing a line for every number I pass in. And one of the nice side effects of using node-canvas is that the above code will also run in the browser so I could generate my histograms client side or server side.

Now that I have my rendering code set up I need to create my web server. This is very basic, but it works for a proof of concept.

http.createServer(function (req, res) {
    var q = url.parse(req.url, true).query;
    try {
        var numbers = JSON.parse(q.numbers);
        var canvas = canvas = new Canvas(numbers.length*WIDTH,60);
        var color = q.color ? '#' + q.color : DEFAULT_COLOR;
        render( canvas, numbers, color );

        res.writeHead(200, { 'Content-Type': 'image/png' });
        canvas.toBuffer(function(err, buf) {
            if(!err) {
                res.write(buf);
                res.end();
            } else {
                throw 'buffer error';
            }
        });
    } catch (e) {
        console.log(e);
        res.writeHead(500);
        res.end('Internal Server Error');
    }
}).listen(8888);
console.log('Server started on port 8888');

That's pretty much it, I just write my canvas out to a buffer and then write the buffer to the client. Now I can use the following to call my service.

http://localhost.com:8888/img.png?numbers=[1,4,1,3,2,1,5,2,2,1,2]

And I get:

image

I can also specify a different color…

http://localhost:8888/img.png?numbers=%5B4,3,2,6,1,3,5,2,4,3,2,4,3,2%5D&color=c3ce69

Which looks like:

image

So this was pretty quick and dirty, but I think that this could prove very useful. You can see that there are all sorts of possibilities where this could be useful.

Check out the full source on GitHub. Thanks for reading.

Links

blog comments powered by Disqus