Tuning RaphaelJS for High Performance SVG Interfaces

Jul 25 2012, 12:30 PM PDT · 2 comments »

Last week, we released a few improvements to our front-end that significantly improved the performance of working with larger schematics in CircuitLab. In this blog post, we detail one of the key components to this improvement. Since it makes modifications to RaphaelJS, a very handy vector graphics library, we thought releasing the details might be helpful to other developers of high-performance browser-based applications.

One of the key interactions we wanted to improve was the performance of zooming and panning around large circuits. The CircuitLab front-end makes use of the Raphael library to draw and manipulate SVGs. In our earlier implementation of zooming, we were using the scale() method on a Raphael Set object that contained all the elements in the circuit. It turns out that the complexity of doing any operation on a Raphael Set is linear with the number of elements in the set. To convince yourself of this, you can read through the Raphael Set source code, or you can see our first experiment below. For each of these tests, I placed N current sources into the circuit, and then recorded how long it took to zoom the schematic from 100% to 200% and back to 100% ten times.

While the roughly ~0.3 seconds needed to zoom a small number of elments was acceptable, as soon as circuits grew to 20 or 30 elements, zooming became quite slow. This meant that our front-end wasn't great for the layout and sharing of complex schematics.

The issue here is that a Raphael Set is essentially implemented as an array that applies any operations on it to each of its items. It's a nice little way of grouping elements together, but unfortunately there is a performance penalty that in this case can be avoided. Luckily, the SVG specification gives us a way to solve this problem in a rather elegant way. SVG provides a group element <g>, inside which you can nest other SVG elements. You can then apply transformations to the group element, and the browser then takes care of propagating the transformation down to the group's inner elements -- even using the graphics card if your system supports it.

To take advantage of this, all we needed to do was find a way to wrap all the SVG elements that Raphael creates into one single big group. This lets us apply SVG transformations (which represent zoom and pan operations) to the group, instead of applying them to a Raphael Set. Implementing this turned out to be a fairly simple modification of the Raphael source code.

Basically, we intercepted the root SVG element as it's being created and assigned as the canvas (the parent of all other elements) within Raphael's internals. Instead, we created an SVG root element <svg> with a group element <g> inside it, and keep a reference to the group. This group element now becomes the canvas for all other Raphael operations. Once we do this, all the SVG elements that Raphael creates will be children of this <g> element.

4747    //these are custom additons from circuitlab to make a single
4748    //group around all the elements
4749    var g = $("g") 4750 cnvs.appendChild(g) 4751 cnvs = g 4752 g.style && (g.style.webkitTapHighlightColor = "rgba(0,0,0,0)"); 4753 //end group modifications  We also added a function called setTransform to the Raphael paper prototype: //special functions added for circuitlab, assumes canvas is actually //a group, not the svg element R.prototype.setTransform = function (trans) {$(this.canvas,{transform:trans});
}


This allows us to set the transform string of the <g> element directly on the paper element. For example:

var paper = Raphael(10, 50, 320, 200);
paper.setTransform('translate(10,10)');


We did have to make the following change in the viewPort method so Raphael could correctly resolve the <svg> element now that we changed what cnvs points to. This change was done on line 4420 of the original Raphael code:

pos = cnvs.getScreenCTM() || cnvs.parentNode.createSVGMatrix(),


Using this method to zoom and pan around our circuits (unsurprisingly) gives us a very significant speedup over utilizing Raphael Sets. Below are the results of the same experiment we ran before:

Note the y-axis values! Here are the results for both old and new experiments with a logarithmic axis for comparison:

These simple changes to the Raphael library allowed us to see massive improvements across our benchmark, with a >100X performance gain at the higher end, and better asymptotic performance which makes larger circuits workable. Give our browser-based schematic capture and circuit simulation tool a spin with one of our example circuits!

One caveat here is that the changes we applied only operate within the SVG module of Raphael. Since CircuitLab doesn't currently support Internet Explorer, this isn't a concern for us, however if you rely on Raphael for IE support you will also have to implement the setTransform() method appropriately in the VML module. Here is a link to the change set that shows the changes discussed in this post.