Using NodeJs to render JavaScript charts on the server

Ideally, you want both rich interactive charts in your front-end (“just like Google Finance” is a common request) and the ability to render the same charts on the server for exporting, emailing, or supporting less capable clients (Blackberry, I’m looking at you). The perfect scenario would be to do this using the same library so that they look identical, and so you don’t have to maintain two separate code bases. Unfortunately due to limitations in libraries, platforms and environments – it’s difficult to make this a reality.

Recently, I’ve been developing a HTML5 application in which we used the Highcharts charting library to provide basic line and bar charts. Highcharts is a JavaScript library that will render either SVG or VML dependent on the hosting browser (if you’re interested, take a look at some of their demos). To support exports, the library will create an SVG in the browser and post it up to the web server. In the box (figuratively), they supply a php script which will render this content into an image or a PDF then return it over HTTP. This works great for situations where we have a full web browser, but sadly, we have to resort to another charting library that can produce static images to produce images on the server. It’s challenging to get the charts looking identical, and maintaining two code bases is a pain.

Unless you’ve had something better to do than constantly follow blogs and twitter (oh, just me then…), you’ve probably heard about Nodejs. It’s a server-side JavaScript implementation running on top of Google’s excellent V8 engine (the one under Chrome). Using the jsdom library, we can provide an environment similar to a browser into which we can load client-side scripts. With very little work, I was able to load up the Highcharts library in node, then using identical code which I would write for a browser, render the chart and grab the SVG content. Compare a chart rendering in a browser to the following from my node sample:

var $ = window.jQuery,
 Highcharts = window.Highcharts,
 document = window.document,
 $container = $('<div id="container" />'),
 chart;

$container.appendTo(document.body);

chart = new Highcharts.Chart({
 chart: {
  defaultSeriesType: chartType,
  renderTo: $container[0],
  renderer: 'SVG',
  width: width,
  height: height
 },
 series: [{
  animation: false,
  data: data
 }]
});

svg = $container.children().html();

Once we’ve grabbed this SVG content, we can render it to an image using the command line tool convert. As it’s super easy in Node to create a basic web server (literally just a single function), I created a basic server which accepts requests like /bar?data=1,2,3,4 and will return the chart rendered as an image using Highcharts.

this.server = http.createServer(function(request, response) {
	var url = parse(request.url, true),
		chartTypeMatch = /^\/(\w+)$/.exec(url.pathname),
		chartType	= chartTypeMatch ? chartTypeMatch[1] : null,

	/* Some code omitted */
	createHighchartsWindow(function(window) {
		/* chart generation from above */

		svg = $container.children().html();

		// Start convert reading in an svg and outputting a png
		convert	= spawn('convert', ['svg:-', 'png:-']);

		// We're writing an image, hopefully...
		response.writeHeader(200, {'Content-Type': 'image/png'});

		// Pump in the svg content
		convert.stdin.write(svg);
		convert.stdin.end();

		// Write the output of convert straight to the response
		convert.stdout.on('data', function(data) {
			response.write(data);
		});

		// When we're done rendering, we're done
		convert.on('exit', function(code) {
			response.end();
		});
	});
}).listen(2308); // Start HTTP server listening on port 2308

So far I’ve only prototyped these ideas, but it’s working pretty well (see image below). Although I’ve used Highcharts for this example, it should be possible to use any SVG based JavaScript charting package. I feel this technique has the potential to be able to support both rich client side charts and static server generated images using the same libraries, and most importantly, sharing the same code. My prototype is available at up at GitHub. What do you think?

Highcharts chart rendered on Node
Advertisements