Link to wealthfront.com

Fork me on GitHub

Thursday, December 22, 2011

Converting dynamic SVG to PNG with node.js, d3 and Imagemagick

Visitors to Wealthfront might notice we're using SVG to render the risk meter in our questionnaire, as well as your performance projection and other charts. We build our visualizations using d3.js, a fantastic library that provides just the right amount of abstraction on top of SVG to allow us to develop robust visualizations quickly. SVG is attractive as a vector format, and animations are simple.

However, as most are aware, SVG is not supported in some older browsers - notably IE 8 and below. One option to provide backwards compatibility is to rasterize SVG to a static image. This saves you from creating a pixel-perfect alternate implementation. While you lose animations, you're still able to convey the important information.

SVG to PNG
As a proof of concept I was interested in any method of SVG to PNG conversion. As you might expect, Imagemagick's convert proved to be a good option. Unfortunately Imagemagick's default SVG support is rather poor. You can solve this by installing librsvg before Imagemagick. Imagemagick will defer to librsvg when doing SVG conversion from that point forward (run convert -list configure | grep DELEGATES and check librsvg is in the list to confirm Imagemagick is using the library). With librsvg support, convert produced PNGs of our SVG source that perfectly matched Chrome's rendering.

We'll get to why we're using node.js in a moment. In the meantime, if we want to use convert from within node.js we can spawn a child process using convert's stdin/stdout argument syntax to pipe data in and out. This saves us having to write to disk and deal with temporary files.

The example below sends the SVG source for a red square to convert's stdin and writes the PNG to the node process's stdout.



But our SVG is dynamic...
Using convert is all well and good when you already have the SVG source, but our visualizations are dynamic - built in the browser with d3.js. This is the more interesting problem, since it means we need a DOM and the ability to execute d3.js on the server-side. For this, jsdom - a DOM implementation in Javascript - is an excellent solution. We can execute our d3 visualization code and it will affect the DOM accordingly. Ultimately, this allows us to extract the generated SVG on-the-fly and pipe it to convert.

Here's an example of jsdom using our "insertPie" function to build the SVG for a pie chart. We extract the SVG with innerHTML.



Altogether now
Now that we can generate SVG, pipe SVG to convert, and get a hold of the PNG output we can chain it all together. Below is a simple example that starts up an HTTP server in node.js and serves our visualization as a PNG image. You can see that it generates a pie chart with values from the query string and pipes the SVG generated in jsdom to convert. Once we receive output from convert we write it to the response stream. The end result is a small HTTP server that will generate rasterized versions of our SVG pie chart.



A step further
At Wealthfront we're nearly all Java on the backend. Should we choose this approach, it would be nice to leverage Java bindings with librsvg for rasterization and a Java-based DOM implementation with JS support to handle generation of the visualizations with d3. Having fewer moving parts in the process reduces complexity, even if we do have to sacrifice the sexiness of node.

The full source for the pie chart visualization and the HTTP server is available in this gist: https://gist.github.com/1509145. Enjoy!