Link to wealthfront.com

Fork me on GitHub

Tuesday, November 30, 2010

When Canvas doesn't cut it: Raphael.js

This is a guest post by Benjamin van der Veen who worked with us to develop a fabulous charting library leveraging vector graphics. Ben is a software consultant specializing in interaction design and development with a focus on iOS, as well as web server and REST web service design. He lives in rainy Portland, Oregon, where the coffee is great and the beer is better.

Given our constant focus on transparency and our open-source commitment, we asked Ben to conclude his project with us by sharing his thoughts, insights and learnings from his experience.


The new Wealthfront website uses the wonderful Raphael JavaScript library to visualize manager performance and other metrics with interactive line, bar, and pie charts. Raphael provides a high-level API which allows you to easily create cross-browser vector graphics.

At the very start of the project, we pondered vector graphics, Flash or using canvas. In the end, vector graphics was the clear win on a number of fronts. Flash (and Flex) were eliminated because of the slow start time of the pluggin. Even with a few elements, page rendering can be significantly slowed down. Using a canvas forces you to redraw the entire context when you want to change your drawing which can be quite expensive when implementing a roll over chart solution.

Raphael is Awesome


SVG embedded in HTML

An HTML file with embedded SVG code must be interpreted by an SVG-capable browser in XHTML mode for it to render properly. If it’s on a local hard drive, give it an .xml or .xhtml extension, or serve it with an application/xhtml+xml content type header.

There are two vector graphics standards supported by major browsers. The most widely implemented one is called SVG, and it is supported by Safari, Chrome, and Firefox. Historically, however, Internet Explorer has not supported SVG (although IE 9 will), and instead implements an alternate standard called VML.

Let’s take look at a simple example of how to draw a red circle with a blue outline in both SVG and VML.

SVG


<html xmlns="http://www.w3.org/1999/xhtml">
<body>
    <svg xmlns="http://www.w3.org/2000/svg" width="300" height="100" version="1.1">
        <circle cx="100" cy="50" r="40" stroke="blue" stroke-width="2" fill="red"/>
    </svg>
</body>
</html>



VML


<html xmlns:v="urn:schemas-microsoft-com:vml">
    <head>
        <style>
            v\:* { behavior: url(#default#VML); }
        </style>
    </head>
    <body>
        <v:oval style="position:absolute;top:10;left:20;width:80;height:80" fillcolor="red" strokecolor="blue" strokeweight="2"/>
    </body>
</html>



SVG and VML are both based on XML, and take a fairly verbose, declarative approach to defining graphics. As a JavaScript library, Raphael takes a procedural approach to defining graphics, and its syntax is less verbose than either SVG or VML. The drawing methods it provides generate SVG or VML code for you, depending on which technology the browser it’s running in supports.

Let’s take a look at how we’d translate the above examples into Raphael:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <script type="text/javascript" src="raphael.js"></script>
    <script type="text/javascript">
        window.onload = function() {
            var r = Raphael(0, 0, 200, 200);
            r
                .circle(100, 50, 40)
                .attr({ 
                    "fill" : "#f00", 
                    "stroke" : "#00f", 
                    "stroke-width" : "2px"
                });
            }
    </script>
</head>
<body>
</body>
</html>

Raphael is arguably prettier and easier to use than either SVG or VML, and—very much by design—it works in all modern browsers. Since you’re working in JavaScript, you can create dynamic vector images on the client-side by programmatically varying your input to Raphael.

The Wealthfront chart implementation needed to very closely match the PSDs from our designer. Raphael was an excellent choice for this purpose, and allowed me to easily replicate the graphics from the PSDs in a very straightforward fashion. However, there were a few details that couldn’t be done in a naive fashion using Raphael. Chief among them was drawing drop shadows, and, as we see later in this post, filling certain shapes with radial gradients.

Cooking Up Some Drop-Shadows


The PSD designs I needed to code up called for drop-shadows beneath the lines in the line chart, behind the bars in the bar chart, and around the pie slices in the pie chart. I knew that both SVG and VML support the concept of filters—bitmap operations which can be applied to vector shapes to render special effects—and I figured that a drop shadow could be achieved using a combination of blur and offset filters. However, Raphael doesn’t support filters out-of-the-box, and for good reason: as it turns out, shipping versions WebKit don’t support SVG filters. Offset-plus-blur still seemed like a good way to approach shadow drawing, so I set about faking it.

WebKit and SVG

WebKit nightly builds do actually support SVG filters. Raphael provides a plugin which could be used to do a blur- or shadow-like effect, but it is not included in Raphael by default, presumably because it doesn’t work with shipping WebKit builds.

In our line chart, the shadowed element is the graph line itself. My approach is to construct two Raphael path strings, one for the graph line, and another for the shadow—the difference being that each point on the shadow path was offset just a couple of pixels to the left and a few pixels below the line path. To accomplish the blur, I render the shadow path string three times, each one with a decreasing stroke width and increasing opacity. The result is a very passable blurry grey line! Finally, I top it off by rendering the brightly colored graph line.



The following code snippet illustrates how the final image was created.

<html>
    <head>
    <script language="javascript" src="raphael.js"></script>
    <script language="javascript" src="jquery.js"></script>
    <script language="javascript">
    $(function() {
        var r = Raphael("stage1", 110, 110);

        var line = "M0 100L25 58L50 56L75 33L100 0";
        var shadow = "M2 103L27 61L52 59L77 36L102 3";

        r.path(shadow).attr({ "stroke" : "#000", "stroke-width" : "5px", "stroke-opacity" : ".02" });
        r.path(shadow).attr({ "stroke" : "#000", "stroke-width" : "4px", "stroke-opacity" : ".03" });
        r.path(shadow).attr({ "stroke" : "#000", "stroke-width" : "2px", "stroke-opacity" : ".05" });
        r.path(line).attr({ "stroke" : "#7bc455", "stroke-width" : "2px" });
    });
    </script>
    </head>
    <body>
        <div id="stage1"></div>
    </body>
</html>

For the bar chart, I use a similar approach to shadow the bars. Before rendering the final brightly colored bar, I render a lightly colored shadow bar centered on the same point as the final bar, but whose borders are one pixel beyond the borders of the final bar and very nearly transparent. I do this a few more times, with each successive shadow bar being a bit larger and more opaque. Again, I top it off with the brightly colored bar.



Check out the following snippet which generated the final image:

<html>
    <head>
    <script language="javascript" src="raphael.js"></script>
    <script language="javascript" src="jquery.js"></script>
    <script language="javascript">
    $(function() {
        var r = Raphael("bar", 110, 110);

        var barX = 35;
        var barY = 10;

        var barWidth = 30;
        var barHeight = 80;
        var shadowSize = 5;

        for (var i = 0; i < shadowSize; i++)
        {
            r
                .rect(barX - i, barY - i, barWidth + i * 2, barHeight + i * 2, shadowSize + i)
                .attr({ 
                    "fill" : "#000", 
                    "opacity" :  (1 - i / shadowSize) * .05, 
                    "stroke-width" : 0 
                });
        }


        r
            .rect(barX, barY, barWidth, barHeight, 3)
            .attr({ 
                "fill" : "90-#b847af-#f369e8", 
                "stroke" : "#b847af", 
                "stroke-width" : "1px", 
                "stroke-opacity" : .5
                });
    });
    </script>
    </head>
    <body>
        <div id="bar"></div>
    </body>
</html>

A Small Gotcha


The design of the pie chart called for the pie slices to have gradient fills, with the pie being lighter toward the center and darker at the edges, as well as a shadow outside the pie. So we needed to be able to apply the radial gradient to a pie-slice-shaped path, with the center of the gradient focussed at the tip of the wedge. But unfortunately, the documentation for the fill parameter of Raphael’s attr method indicates that radial gradients are only supported for circles and ellipses.

I thought this was strange—I knew that both the SVG and VML specifications supported filling arbitrary paths with radial gradients, so I emailed Dmitry Baranovskiy, the author of Raphael. He quickly got back to me with the answer: it turns there was a bug in IE’s implementation of VML related to radial gradient fills in arbitrary paths. Because the stated goal of Raphael is full cross-browser compatibility, it can’t with good conscience fully support radial gradients if it can’t render them in Internet Explorer. Many thanks to Dmitry for his prompt and informative response!

In the end, this was a show-stopper for rendering our pie chart using Raphael. In order to support both IE and SVG-capable browsers, it was clear that we would need two separate implementations. We decided to go with server-side rendering of the pie chart graphics at first, and later added support for those browsers which do support SVG via the jQuery SVG plugin. Using jQuery SVG, I was able to create radial gradients for both the brightly colored pie slices, as well as the shadow outside the slices.

In Summary


Overall, Raphael is an amazingly capable and easy-to-use library which saves the pain of dealing with SVG and VML separately. While browser support for filters isn’t ready for prime-time, it’s easy to fake a drop-shadow effect in a cross-browser fashion by drawing several grey shapes which are larger than the object to be shadowed, with each shape getting more opaque and closer to the size of the shadowed object. The use of radial gradient fills in Raphael is limited to ellipses, but that’s a small constraint for a properly-informed and talented graphic designer targeting Raphael. Raphael provides incredible power and flexibility for implementing beautiful, dynamic vector graphics.