Cross Platform Scalable Vector Graphics with svgweb
Issue № 323

Cross Platform Scalable Vector Graphics with svgweb

Pity Scalable Vector Graphics. It’s been an official standard since before IE6 was released yet has never found much of an audience on the web, certainly not the one it deserves. Just as SVG was starting to establish some browser support, along came the canvas tag, stealing the thunder of dynamically generated client-side images.

Article Continues Below

Despite all the attention being paid to canvas, there’s still a place for good ole SVG, particularly for developers looking to replace plug-ins like Flash for data visualization.

Canvas and SVG#section2

One of the reasons that Flash has been a popular choice for data visualization is the rich graphics API that was introduced several versions back. This graphics API, native to Flash’s programming language ActionScript, makes it possible to draw dynamic charts, graphs, and maps programmatically, based on data or user input—without having to fiddle with any server-side middleware, and all in the browser. This was a revelation when it was introduced and drove a great deal of innovation in interactive info graphics.

A few years ago, Apple introduced the canvas tag to allow developers building Dashboard widgets a mechanism to draw arbitrary shapes using just HTML. That proposal, as you no doubt are aware, is now an official part of the HTML5 spec. The drawing methods canvas introduced are actually quite similar to the ones that ActionScript developers have been using for a while. In fact, I’d wager that anyone who’s done any dynamic drawing in Flash could move over to canvas pretty easily. I’d double-down on that bet and say that the similarity between drawing with ActionScript and canvas is at least partially responsible for canvas’ success.

There are also JavaScript-based drawing libraries like Raphaël—in fact, you’ve probably already seen Brian Suda’s excellent article on using the Raphaël JavaScript library to render SVG dynamically in the browser. Raphaël is great for abstracting a lot of the complexity of SVG and for working cross-browser, all the way back to IE6; but one thing it doesn’t do is let you load external SVG files, similar to how you would load an image using the img tag.

What does the ability to load an SVG file have to do with anything? Well, for one, it means that you can use third party tools like Illustrator or the excellent (and free!) Inkscape for drawing your SVG images, which can then be loaded and manipulated in the browser using JavaScript. The difference is all about how you approach the problem—ActionScript, canvas, and Raphaël let you draw images programmatically with code, while SVG lets you draw images in a drawing tool, load that image into a browser and add scripting.

To make all of this work, we’re going to be using an open-source JavaScript library called svgweb to load the SVG file, add events, and handle cross-browser issues. The way svgweb handles older browsers, like IE, is really quite clever: Rather than try to map SVG to IE’s proprietary drawing APIs—which is what Raphaël does—they built their own SVG renderer in Flash. That Flash-based SVG renderer loads the external SVG files and also handles JavaScript events that are passed from the browser, completely transparently.

The svgweb library detects if the browser has native SVG support and, if not, it embeds the Flash shim that translates SVG into Flash-native drawing commands. (This is similar to how some HTML5 audio and video players are using Flash as a fallback for playing H.264 encoded video in browsers that don’t support the video tag or H.264 encoded video.) DOM-style events are handled and passed back and forth between the Flash shim and the browser so that you only have to write the event logic once, in JavaScript, instead of having to write ActionScript handlers as well. This means that svgweb brings SVG to something like 95% of browsers, including mobile devices like the iPhone and iPad that handle SVG natively. (Android devices don’t currently support native SVG rendering, but they’re working on it.)

Before we go further, I just want to point out that I’m not advocating one method over another; I think there’s actually room for canvas, Raphaël, and SVG. There’s probably even overlap, which is good news for developers because it lets us pick the approach that works best for our circumstances. I’ve found that canvas seems particularly well suited for immediate, client-side raster graphic rendering; simple charts are a great use for canvas and the excellent Flot library makes building charts with JavaScript incredibly easy. SVG is well suited for displaying complex shapes, like a map, that you might prefer to draw in an application like Illustrator first and then use JavaScript to link dynamic data on top of it.

A data map with SVG#section3

Here’s a request my team of designers and developers at msnbc.com gets all the time: Create a map to illustrate a news story. Maps are handy and we’ve developed a set of templates to quickly publish them, some of which are built on top of existing tile-based services like Google or Bing, some of which are custom Flash applications. Plenty has already been said about the power of building on top of something like Google maps or even rolling your own tile-based map engine. For info graphics, however, tile-based maps can be overkill or even distracting; it’s nice often to have a custom drawing.

A classic example would be a nation-wide map showing data by state, like the red/blue maps on election night. For this example, we’ll use data from the most recent U.S. census to show which states gained or lost representatives.

Before we get started, there’s one bit of configuration to set up. Because some browsers have a hard time loading the necessary script and Flash file directly from your computer’s filesystem, it’s best to serve them from a web server. You could upload everything to your web host but you probably have a webserver built right into the computer you’re developing on. On a Mac, create a new folder called svg-info in the Sites folder in your home directory, then go into System Preferences > Sharing and enable Web Sharing. Point a web browser to http://localhost/~your-username/svg-info (replacing your-username with your actual username) and you’ll see your svg-info folder in the browser.

With that done, you’re going to need a map, in SVG. These can come from a variety of sources—the designer you’re working with could build one in Illustrator or use a tool like ArcGIS to generate one from a shapefile. Or you can nab one from Wikipedia, which has a whole collection of free SVG maps, including a blank US map with each state as its own labeled shape. Properly labeling each shape is important when it comes time to link the data with the map; fortunately, SVG files have a DOM much like HTML files, with each state contained in its own labeled node. This is definitely something you’ll want to keep in mind if you’re generating your own maps or shapes.

This map’s a little big for most web pages, so the first thing you’ll want to do is size it down a little, using Illustrator or Inkscape, both of which will open and edit SVG files natively. This already shows one of the advantages of using SVG—a designer can open and edit vector files using the tools she uses every day. I sized the map down to 500 pixels wide so that it would fit on a page. Make sure to size the containing document down as well. (In Illustrator, go to File > Document Setup > Edit Artboards then set “Fit Artboard to Artwork bounds” from the presets.)

Next, you’ll need some data, which we’ll write into the page as a table to make it as accessible as possible. The table will look something like this:

<table id="results-data">
  <tr>
  <th>State</th>
  <th>Change</th>
 </tr>
 <tr>
  <td>AL</td>
  <td>0</td>
 </tr>
 <!-- snip! -->
 <tr>
  <td>WI</td>
  <td>0</td>
 </tr>
</table>

Now, this table doesn’t exactly reflect the sort of real-world complexities you run into with real data, but it should do for our purposes.

You might ask why write out a table at all if the purpose is to show a graphic and the answer is all about making your data accessible. Search engine spiders and screen readers, even JavaScript-aware ones, probably won’t be able to understand the data on your map in any kind of a meaningful way, so this table will help them index your data. SVG is, in theory, more accessible than raster graphics like JPEG, GIF, and PNG (for one, SVG is scalable so readers with low vision can zoom in without losing fidelity) because the image itself is described as XML. The reality, however, is that support for accessibility in SVG is still fairly nascent so you can’t count on screen readers being able to access the information in SVG. A humble table will work wonders here.

With that data on the page, now we’ll start writing some JavaScript to do something more interesting with it. First, download the latest svgweb files (the latest official release is from this past August but a new one is coming any day now) and unzip them.

Open the svgweb folder you uncompressed, then open the src folder and copy svg.js, svg.swf, and svg.htc to a folder on your webserver. Using a subfolder like js is fine, just make sure to keep all three files in the same folder. Now, include the svg.js as the first script tag in the head of your HTML file.

<script src="js/svg.js"></script>

Now, let’s write a function that will grab the data from the table and convert it into a JSON object. Drop this in a script tag after the script source to include the svgweb library (Line wraps marked » —Ed.):

//convert table to json
//@param t is the id of the table (string)
//returns a json object with an array containing an object for each row
function tableToJSON(t){
    var table = document.getElementById(t),
        header_row = table.rows[0],
        n_headers = header_row.cells.length,
        n_rows = table.rows.length,
        keys = [],
        n_keys = 0,
        json_data = [],
        json = {};    //rip through every row to create the JSON data objects
    //assume the first row contains the headers
    for(var i=0; i < n_rows; i++){
        if(i===0){
            for(var j=0; j < n_headers; j++){
               keys.push(header_row.cells[j].firstChild»
.nodeValue.toLowerCase());
            }
        } else{
            var this_row = table.rows[ i ]
            var data = {};
            n_keys = keys.length;            for( var j=0; j < n_keys; j++){
                data[keys[j]] = this_row.cells[j].firstChild.nodeValue;
            }            json_data.push(data);
        }    }    json[t] = json_data
    return(json);
}
   

There’s really not much to this function—it takes as a single parameter (the name of the table) and then loops through every row to assemble and return a JSON object. It assumes the first row of the table are labels for each column, which are then used for each of the name value pairs. Given the table above, our JSON object will look something like:

{ "results-data": [
    {"state": "AL", "change": 0},
     //... snip! ...
    {"state": "WI", "change": 0}
  ]
}

(One quick production note: this is an attempt to keep the code accessible without delving too deeply into the minutiae of backend implementations. Your needs might require a separate JSON feed from your database, for instance.)

Enough about the data, let’s add the map. We’re going to use an object tag to load the SVG map as an external resource, with a conditional comment to deal with IE. The IE-specific object uses a few different parameters, namely src instead of data and classid instead of type. Also note that of the SVG files you load, most come from the same source (your webserver) as the HTML file itself—no cross-site scripting allowed.

<div id="results-map">
   <!--[if IE]-->
       <object src="states.svg" classid="image/svg+xml"
           width="500" height="375"
           id="state-map" alt="Interactive map graphic showing»
electoral gains and loses per state as of the 2010 census">
   <!--[endif]-->
   <!--[if !IE]-->
       <object data="states.svg" type="image/svg" 
           width="500" height="375"
           id="state-map" alt="Interactive map graphic showing»
electoral gains and loses per state as of the 2010 census">
   <!--![endif]-->
       </object>
</div>

This should be pretty straightforward—the src/data attribute (depending on the browser) points to the SVG file; the width and height should be set to at least as large as the size you saved the SVG file earlier. You should also give each object the same id for later reference. Note that setting the width or height in the object tag won’t affect the size of the embedded file, just the object containing it. If the object’s width/height are smaller, you’ll see scrollbars; bigger, you’ll have whitespace. The alt attributes will provide some guidance to bots and screen readers.

If you load this page in your browser, you’ll see a gray map of the US of A. And if you use the browser’s native zoom in and out, you’ll see that the graphic is, in fact, scalable.

Now, it’s time to link the data to the map. As is so often the case with JavaScript programming, you want to make sure the map has loaded before you try to access it with a script. Fortunately, svgweb provides an onsvgload event that you can listen for that fires once the SVG has loaded. We’ll have it call an init function that will handle the data-to-map processing:

function init(){}window.onsvgload = init;

Inside of that init function, we need to first grab the map, using the standard getElementById with an extra property, contentDocument, which lets us grab the SVG itself.

var map_svg = document.getElementById('state-map').contentDocument;

Once you’ve grabbed the SVG map, it behaves similarly to an ordinary DOM object.

Next, we’ll call our tableToJSON function to get the data.

//tableToJSON will return a JSON object with our data
var map_json = tableToJSON('results-data');
//grab the actual array from the json object
var map_data = map_json['results-data'];
//get the array length so you don't calculate it»
for every loop iteration
var n_map_data = map_data.length;

Now, we just loop through the results and apply the data to our map.

//loop through the map data and assign properties»
to each state on the map
for(var i=0; i < n_map_data; i++){
    //get the state on the map based on the ID in the data array
    //in this case, the ID is the state abbreviation
    var map_state = map_svg.getElementById(map_data[ i].state);    if(map_data[ i].change === '-2 or more' ){
        map_state.style.fill = '#B26600';
    } else if (map_data[ i].change === '-1'){
        map_state.style.fill = '#FF9200';
    } else if (map_data[ i].change === '+1'){
        map_state.style.fill = '#19ABFF';
    } else if (map_data[ i].change === '+2 or more'){
        map_state.style.fill = '#0071B2';
    }    //assign the state a title, in this case the name of the state»
and the change in seats
    map_state.title = map_data[ i].state + '»
will have a change of ' + map_data[ i].change + ' seats';    //give each state an event
    //for simplicity, the event is a simple alert that displays»
   the title param we just assigned
    //yes, addEventListener even works in IE, thanks to svgweb
    map_state.addEventListener('mouseup', function(e){
        alert(e.target.title);
    }, false);
}

What this loop does is first look for any SVG node with an ID that matches the state in our data array—both are abbreviations such as “AL” for Alabama. When it matches the state on the map to the state on our data array, it colors the state according to the change, from a dark orange to dark blue.

Finally, we give each node a title tag. This will let us access this information when we assign a click handler to each state. You may notice that we use addEventListener, a DOM-but-not-IE-supported standard for listening for JavaScript events. Thankfully, the smart folks behind svgweb have handled that for us and have abstracted event handling to use the standard model. This is a very simple event that pops up a JavaScript alert with the title that we just assigned.

Like it’s 1999#section4

SVG has held a lot of unrealized promise for over a decade now, with baked in support finally becoming mainstream (even IE9 will include native SVG rendering when it launches later this year). Unlike canvas or other script-only approaches, SVG can be easily divided into design and code elements, with just a little code to add interactivity. It even works on devices like the iPad and iPhone. And now, thanks to svgweb and a clever use of Flash, it works on older platforms no one could have ever imagined supporting SVG.

It’s time to add SVG to our arsenal of tools for building an amazing web.

About the Author

Jim Ray

Jim Ray is editorial development lead at msnbc.com, where he manages a team of journalists, designers and developers who are all sharper than he is. He’s worked at the intersection of media and technology for a decade now. He occasionally writes about food and cracks wise on Twitter.

15 Reader Comments

  1. Thanks Jim for this information. My current and first attempt at SVG support for an iPad web app is to use canvg (http://code.google.com/p/canvg). Coincidentally, my test graphic is also a USA map which as SVG graphics go is not terribly complex. The good news is that the image renders on iPad, but slowly. So slowly that the logic I added to let the user scale the image to zoom in and pan brought the iPad to its knees — it just froze. Even on my deskstop machine, performance in Firefox is sluggish. In contrast, zooming and panning a jpeg image rendered on the HTML5 canvas is fast on a PC and sluggish on iPad, but at least it’s usable. Have you tried svgweb on iPad to see how it performs?

  2. I have a temperature monitoring app, which mainly runs on iPhone/iPad. Nothing special, but showing a couple of graphs for the temperature/humidity (etc…) flow for an adjustable time-frame. This one used to use Flot (jquery.flot to be exact). Recently I replaced the plotting function with a SVG plot (tried Raphael as well), but finally switched back to flot, because of the drawing speed.

    While flot draws a set of 5 flow-charts in a mere 150ms, a bare-bone SVG-plot took more than a second for the exact same graphs (on the iPhone). On my iMac i7 it was great, but on the iOS-devices it’s just not snappy enough.

  3. I was thinking of using SVG for vector based designs (Logos, Badges, icons, etc.) to use in a responsive design I’m working on. That way when the browser window is resized these items would still look crisp.

    What do you think of this type of implementation? This seems like a simple and useful way to work with SVG.

  4. With regards to iPad and other mobile devices, I’ve done a little, by-no-means exhaustive, testing. The svgweb library uses the native SVG renderer built into iOS, which is actually pretty great. The example map from my article renders quite quickly on my iPhone 3GS and iPad, with the Javascript events handled as you’d expect.

    I’ve done a little bit of testing with Raphael and, anecdotally, I’d say that it’s slower than the native SVG rendering, which you’d expect. This isn’t to say that you shouldn’t consider Raphael for mobile devices, it really does depend on the kind of application you’re building.

    For charts and graphs, which tend to be graphically simpler than something like the map in my example, I really do recommend using something like Flot.

    There really is a place for all of these different approaches, and even a mix. It’s an amazing time to be a web developer.

  5. Rheal, that’s an interesting question. I think SVG would help out with responsive designs in some situations — logos, background images, that sort of thing. For icons and badges, I’d probably stick with jpgs or gifs.

    Bear in mind that the client still has to draw the image once it parses the XML that describes it. For desktop machines, this isn’t such a big deal but for mobile devices, if you’re throwing a lot of SVG images in the form of icons, it could cause some slow render times.

  6. Flash isn’t a problem on Android platforms, and with android based tablets coming… flash will preform even better and faster for mobile. Although I’m very optimistic for html5 and the future of it, but it still has a long ways to go before it can be where Flash is now.

  7. Just a note that the object code to load the svg image doesn’t appear to work in Safari (5.0.3).

    Great article otherwise.

  8. FWIW, might have just been the IE comment format, and/or the addition of the “+xml” to the non-IE object type.

    <!–[if IE]>
    <object src=”states.svg” classid=”image/svg+xml”
    width=”500″ height=”375″
    id=”state-map” alt=”Interactive map graphic showing electoral gains and loses per state as of the 2010 census”>
    < ![endif]--> <!--[if !IE]>–>
    <object data=”states.svg” type=”image/svg+xml”
    width=”500″ height=”375″
    id=”state-map” alt=”Interactive map graphic showing electoral gains and loses per state as of the 2010 census”>
    <!–< ![endif]--> </object>
  • I did manage to construct my own map and making it work in FF, But not in IE. Even your example doesn’t working in IE7 or IE8. What may be possibly happening?

  • SVG is great, unfortunately its not supported by Android (because they wanted to save 1M on the disk image! http://code.google.com/p/android/issues/detail?id=1376) and this stops it from being an end-all-be-all solution. It runs decently on iOS, but like canvas, its performance is not great.

    Benchmarks between SVG and Canvas show that SVG tends to get slower, quicker, when dealing with complex visualizations.

    For flash people: Its the same tradeoff experienced in Flash when drawing complex vectors with the Drawing API versus changing pixels with BitmapData.

    If you are targeting an iOS device the best thing you can do for complex effects is use the DOM and CSS3 because Apple has hardware-accelerated CSS3 (+ webkit-transitions) on iOS.

  • It seems SVG is becoming more and more prevalent, but I’m struggling to find information on how it’s currently supported across all the devices (particularly mobile)… ended up here on a search… maybe it’s time for an update on this article to cover the current state of device compatibility?

  • Got something to say?

    We have turned off comments, but you can see what folks had to say before we did so.

    More from ALA