Build a Tweet map with SVG

Divya Manian of nimbupani.com reveals how to create dynamic visualisations using Polymaps, a library that makes it trivial to create and manipulate map data in SVG

Shares

Ever since IE9 announced support for it, SVG has suggested itself as a credible solution to consider for your visualisation needs. In this article, I’d like to introduce you to Polymaps, a great way to create map-based visualisations. This will work on all browsers that recognise SVG.

The advantages of using SVG for such visualisations are many: smaller file size, superior visualisation features, less intensive processing and faster animations. Moreover, these will work on your beloved iPhones and iPads! Here we’ll use Polymaps to chart locations from where people tweet based on an archive of Twitter’s public timeline. We’ll learn:

1. How to get geographic location data in a JSON file2. How to make a map of the world with just a few lines of code3. How to interact with SVG using JavaScript (with a little help from jQuery)

While we’re using tweets for this visualisation, you can use any data you have location information for, including BrightKite, Foursquare, Gowalla check-ins, CIA World-book information or perhaps some data that your web application outputs.

Take a look at twitter.json in the tutorial files to note how I’ve captured the data that we’ll be rendering on the SVG map.

Introducing Polymaps

Polymaps is a JavaScript library that generates maps similar to Google Maps or Open Layers. As well as supporting image tile sets for maps (such as Google Maps), it also supports vector tiles that can be rendered with SVG.

The vector geometry is loaded via GeoJSON and the objects are rendered as SVG elements – circles, paths, etc. When using vector tiles, the rendering is trivial in terms of performance and faster because it does not have to make as many trips to the server as the image tile sets. Polymaps also provides a variety of functions that enables you to add or remove geographical locations, and trigger events through JavaScript.

Because Polymaps uses SVG, you can simply use CSS to style the vector data (as well as all the powerful filters and gradients available in SVG). In this tutorial we’ll use a JSON file that contains the co-ordinates for a world map (this file is available in the world example folder when you download Polymaps).

Polymaps has sparse documentation. But there are lots of examples online (see polymaps.org/ex – full source found in the zip file), which you can fiddle with to understand Polymaps better. Let’s get started by first rendering the world map with the JSON file.

Rendering the map

First, we should include the Polymaps Library in our HTML file before we include our script. We’re using jQuery for small manipulations as well, so let’s include that too:

We use the protocol relative URL to load jQuery (paulirish.com/2010/the-protocol-relative-url). We also include a local copy so the demo works even in the rare event of a failure to retrieve the file from Google’s servers. We then initialise a map object that Polymaps can use and identify its parent container:

We create an SVG Element and append it to the #map, then pass on #map to the container() so Polymaps knows where the SVG file needs to be outputted. .center() takes in geographic coordinates you want the map to be centred at (in the above code, those are the geographic coordinates of the Royal Observatory at Greenwich). Like other maps, you have 10 levels of zoom to play with. You can also set the zoom range, which sets the max and the minimum zoom levels users can use (for more features, see polymaps.org/docs/map.html). Now, we shall parse the world.json file that includes each country and its location information as objects. We add it as a layer on the map:

po.geoJson constructs a GeoJSON layer based on the template passed via the .url() function. You can either pass a JSON file or a URL string with placeholders for returning image tiles (see this example: polymaps.org/ex/pale-dawn.html) to url().

If you’re calling the JSON or the image tiles from a host that’s different from where your application is hosted, make sure that server has a suitable Access-Control-Allow-origin header set for the returned data to be available.

You get two events with a map layer: load and show. In the code above, the manipulatemap function will be called once the map is loaded. I also want the users to zoom in and out but not to pan, hence I specify:

map.add(po.compass() .pan("none"));

You want your countries to show up, so it’s clearer where the tweet is coming from. We can do this once the JSON file has been parsed into the SVG Map layer, so we do this in manipulatemap(). When the load event is triggered, it passes the features object used for creating the map from the geoJson file. Each object in the features object contains the associated SVG element (in this case a SVGPath element) and the associated information from the world.json used to create it. Now it’s just a matter of setting the right colour for each country. Instead of fretting about colour choices, how about doing it dynamically?

We want to ensure the colours are cohesive. Here, using the HSL notation would help. We set a hue, and only vary the saturation (randomly) and lightness (within bounds):

We set the hue to 29, which gives us a nice brown base to work from. We vary the saturation levels (Math.random() provides a value that’s between 0 and 1). However, we want to control the lightness to not vary too far to the edges (so it only varies between 30 and 90%). Now we have a smooth earthy tone for all the countries while they are accented with saturation and lightness. The only caveat is that Safari, both on desktops and mobile devices, doesn’t like HSLA in SVG – so all you see is a dark black fill. If you’re not too particular about the colours, you could use RGBA or even hex units so that the colours are visible on all browsers.

At this point, let’s create the SVG elements that will be used for rendering the tweets. We’ll only modify the content and the coordinates so that we don’t have to keep destroying and creating new elements for each new tweet.

po.svg is a function available from Polymaps to initialise an SVG element in the SVG namespace. We can use it to create all SVG elements, including elements to represent the text for the tweet and the location.

Notice the if statement? We’re querying the browser to tell us if it implements a certain feature of SVG specification. The feature we want to use is called textArea. It enables you to constrain the width of the text, unlike the text element, which doesn’t do any word wrapping. No popular browser implements this part of the spec except for Opera. Luckily for us, browsers do not lie about this either, so we can use this function safely to detect (and use) textArea if implemented; otherwise the text element.

We’re not using jQuery functions here because some of the methods used by jQuery require functions that are available to HTML elements but not SVG elements. We append the tweet and the circle to our grouping element and append the grouping element to our root SVG Element.

Loading the Twitter content

Next, we parse our Twitter data in showtwitter(). We need to translate the geographic location data in our twitter.json to the pixel coordinates on the SVG map. This is easily done by locationPoint(), provided by Polymaps. This translates geographic locations into SVG pixel coordinates relative to the map container:

tweettext.firstChild && tweettext.removeChild(tweettext.firstChild); // remove old tweet if there is one tweettext.appendChild(document.createTextNode(tweet['tweet'])); //add the next tweet

We finally call the showtwitter at regular intervals to show a new tweet on the map.

Among the popular browsers, only IE8 and below do not support SVG. This demo will not work on those browsers. You could use this script (from diveintohtml5.org/everything.html#svg) to detect support for SVG and if there is no support, render a graphic or embed a Flash file: