Introduction to SVG scripting: an interactive map

This is a brief introduction to how you can create make an SVG interactive using ECMAScript. For an introduction to how to general SVGs please take a look at my SVG tutorial. As an example, I'll create an interactive map, which seems to be a popular use for SVGs. I've written an overview of alternative methods for adding interactivity to an SVG here. There are basically only four functions you need to know:

  • getElementById("X") - gets the element with an id of X
  • element.getAttributeNS(null, "X") - gets the value of attribute X
  • element.setAttributeNS(null, "X", "y") - sets the value of attribute X to y
  • element.firstChild.data - refers to the text in a text element

Once you know how to use these (and how to trigger events), you can create all sorts of impressive effects with minimal effort.

Step 1. Get some country-based data

The first step is to get some data. Presumably, if you want to create a map, it's because you have some data you display. If you're looking for sources of digital data, you could try the world bank, the Guardian Datastore, data.gov.uk (for UK-based data) or data.gov (for US-based data). For this tutorial, I'm going to use some data from the WHO, but it's actually quite tricky to extract data from their website.

Step 2. Get a map

Now you need an SVG map. You can either draw your own in a program like Inkscape or Adobe Illustrator (or you could theoretically write the SVG from scratch), or you can download an open source SVG map from Wikimedia. If you do get a map from Wikimedia or some other source, you'll may have to clean it up (moving elements out of groups) to get all the interactions to work correctly. A key feature your map needs is for the country elements (almost certainly paths) to have an id attribute that gives their name.

For this tutorial I'm using a map of Africa that I got from Wikimedia and spent way too long cleaning up. I've also add a CSS hover effect (described here). You can download the map at the bottom of this page under Attachments.

Step 3. Initialise the SVG

In my simple ECMAScript example, I only changed the attributes of elements passed by evt.target. However, to have more control over the SVG DOM, we need to be able to refer to our SVG. We can do that by creating the following function.

<script type="text/ecmascript">
<![CDATA[
  function init(evt) {
    if ( window.svgDocument == null ) {
      svgDoc = evt.target.ownerDocument;
    }
  }
]]></script>

We can now use the svgDocument object. Note that all of our code must be written in the <script> element and CDATA[ ] label.

Now we need to call our function, which we do with the onload event in the <svg> element: the svg tag should look something like this:

<svg width="400" height="400" version="1.1"
xmlns="http://www.w3.org/2000/svg" onload="init(evt)" >

Step 4. Display country names

The first script effect I'll demonstrate is how to change the text in a <text>. This can be used to achieve a variety of effects, such as displaying the name of an object when the mouse is held over it (see my tooltip tutorial). For this tutorial, we'll just display the country name at the bottom of the image. In my previous interactive SVG map I achieved this effect using the set command, but that's quite inefficient as it requires a separate text element for every country. Using ECMAScript is a more efficient method.

First, we add an empty text element to the bottom of the image:

 <text class="label" id="country_name" x="10" y="390"> </text>

I've given it a class, so it's easy to style, and an id, so I can target it within a function. I've also made the <text> element contain a single space; if you don't have anything here, then the text's value will be null, which will cause problems in the next step.

The next step is to create a function (within the <script> tags) that changes the text value:

function displayName(name) {
  svgDoc.getElementById('country_name').firstChild.data = name;
}

This function selects the element with the id 'country_name', and sets its firstChild.data, i.e. the value between the tags, to equal to a passed name. To call the function when a country is moused over, you need to add an onmouseover event to each path or group tag making up a country. The event should call the function and pass the relevant name:

onmouseover="displayName('Whatever country name')"

It's a bit of a pain to manually add this code to lots of countries so using Python to edit the XML is a good idea if you know how to. The map should now look something like this:

5. Colour a country

Assuming each path or group of paths representing a country has a sensible name (and this is where it pays to have a good starting map), it's easy to colour a country. For example, we can colour Libya green:

country_id = 'libya'
colour = '#004400'
country = svgDoc.getElementById(country_id);
country.setAttributeNS(null, 'style', 'fill:'+ colour);

However, if you're making a chloropleth map, you are likely to group countries into a few different classes and associate each class with a colour. For this, it makes sense to use classes and CSS. This will also make it much easier to change colour scheme later if we so wish.

So first we define a few colours within the style element:

.colour0 {fill: #b9b9b9;}
.colour1 {fill: #ffa4a9;}
.colour2 {fill: #cc6674;}
.colour3 {fill: #993341;}
.colour4 {fill: #66000e;}

Then we can define a function that finds the class of a given country and adds an additional class of "colourX", where X is a given number.

function colourCountry(name, colour) {
   var country = svgDocument.getElementById(name);
   var oldClass = country.getAttributeNS(null, 'class');
   var newClass = oldClass + ' colour' + colour;
   country.setAttributeNS(null, 'class', newClass);
}

For example, we could colour a Algeria with our third colour like so:

colourCountry('algeria', 2)

6. Colour multiple countries

We can use the function we just defined to colour many countries at once with a loop. The following function colours an array of countries with a given class number: 

function colourCountries(data, colour){
    for (var country=0; country<data.length; country++){
        colourCountry(data[country], colour);
    }
}

We use this function like so:

var data1 = ['ghana', 'togo']
var data2 = ['burkina-faso', 'cameroon', 'chad'];
colourCountries(data1, 1);
colourCountries(data2, 2);

It should be pretty clear that we can generalise this approach with another loop, although it does require creating an array of arrays. Although this is perhaps not the intuitve data struture, it is easy to program. We can arrange our data in an array, such that each value in the array is an array of countries that share a colour. For example:

var data1 = [['ghana', 'togo'],
             ['burkina-faso', 'cameroon', 'chad'],
             ['congo', 'cote-ivoire']];

In this example, Ghana and Togo are given colour 1, Burkina Faso, Cameroon and Chad are given colour 2 and Congo and Cote d'Ivoire are given colour 3. We can transverse this array of arrays and add the colours with an updated colourCountries() function:

function colourCountries(data) {
  for (var colour=0; colour<data.length; colour++){    
    for (var country=0; country<data[colour].length; country++){
      colourCountry(data[colour][country], colour+1);
    }
  }
}

AttachmentSize
Blank Africa Map.svg166.46 KB
Blank map display names.svg168.39 KB
colour_with_class.svg167.05 KB

Comments

Thanks for this example.  Can you suggest a way that might also work with SVGWeb on IE 6,7 or 8?

Thanks! - Dan

Hi Dan,

I've had no experience with SVGWeb so I'm afraid I can't help. I have no need to use IE, so have avoided it. Sorry.

This really helps to my project. And in addition I would like to know the continuation of your tutorial regarding on how to colour the map with the data.

Thanks.

- Yal

Hi,

Many thanks for all your tutorials about svg, I'm just a beginner and they are very helpful.

Could I ask you how would be the code to when mousesover see text but when mouse is out text to go off. Also the text should be at differents positions, every word in a different position.

The idea is a bars graphic, hover every bar and she the data on top of graphic.

Hope you can help

Many thank

 

 

Well, I would use a tooltip (tutorial at http://www.petercollingridge.co.uk/interactive-svg-components/tooltip), as I did here: http://www.petercollingridge.co.uk/sals-wise-words/simple-sentence-counts

You could change the tooltip code so that the x and y position is determined by position of the bar. A simpler, but less flexible method would be to use the <set> tag like this: http://dl.dropbox.com/u/169269/labelled_columns.svg

Hi Peter,

Many thanks for your help, I used the last option, the <set> tag and it works perfect.

Thanks for your time, I really appreciate. 

Regards

Hi Peter,
This tutorial helped me a lot with my project. Many thanks.

 

Hi when I save your svg demo to my web server and when opened in a web browser. It is a saving the file and not showing the svg map.

example link below

http://14.140.244.52/clients/ixia/Maps/colour_with_class.svg

Can you help on this.

Hi Arulmurugan,

It sounds like your server doesn't know how to deal with SVG - you need to set the mime type. How you do this will depend on the server. This link might help.

Can anyone tell me why the name is not appearing when one hovers over a state? Thank you so much for this tutorial!

http://ericflorenz.com/us.html

Hi Eric,

You need to add a space into the text element so that it has some child data. i.e. Change the penultimate line from:

<text class="label" id="country_name" x="0" y="0"></text>

To:

<text class="label" id="country_name" x="0" y="0">  </text>

You may also need to make the x and y coordinate bigger so the text is written on the map.

It works. Thanks so much Peter. I am quite fluent in Flash and not so much in HTML. Making the leap is much easier with a great support community.

hi am doing a project where am expected to use a map to give data on each country that is clicked.am struggling to follow your tutorial, how do you id each country on the map and give the right data.Thanks again

Hi Migayi,

You have to add the IDs to the map manually I'm afraid. I'm not quite sure what you mean about giving them the right data. If you email me with more details I might be able to help.

Sorry for my stupid question: I only know that for example adobe flash allows to make the interactive map, and upload to a webpage....but when it comes to your code here, should I put it just in the webpage?

Also, can I use dreamweaver for example to visualize the code when making?

Hi Ximu,

There's a few ways to add SVG to a webpage, which I've described here: http://www.petercollingridge.co.uk/svg-tutorial/adding-svg-webpage

I've never used Dreamweaver before so I don't know what it can or can't do, sorry.

Thanks a lot, Peter!

So if I understand correctly, you recommend to store the file .svg somewhere, and then call this .svg file using object or embed.

What I had in mind is some map like this:

http://www.economist.com/blogs/graphicdetail/2012/11/us-election-2012?spc=scode&spv=xm&ah=9d7f7ab945510a56fa6d37c30b6f1709

so pretty similar to your map here. The reason I talked about dreamweaver is to have something like what you see is what you type, so that I can see the map generated from the code right away if it's a bit complicated map. sorry for this stupid question... but I am really not very good at this.... Many thanks!

Hi Ximu,

Yes, I would put the SVG in the same folder as the HTML file (or put it in a subfolder and change the path to it), then use object (or embed - I'm not sure if there's a difference).

The question about Dreamweaver is not stupid, I just don't know anything about it, so can't help. It sounds like it will work fine with Dreamweaver.

Very useful, thanks. I wish there were more guides like this for SVG.

Regarding your onmousemove event, you can avoid all literal string entries by putting a single event on a group enclosing the territories and using its event parameter to get the relevant territory (event.target).

All of this was very helpful to me, thank you!

I have one question: is it possible to change child data of text element but not to use scripting?  I need clear svg code, so I did something like this:

<text id="textID" .... visibility="hidden">sometext</text>
<line id="lineID" .... >
<set attributeName="opacity" from="1" to="0.5"   begin="mouseover" end="mouseout"/>
<set xlink:href="#textID" attributeName="visibility" from="hidden" to="visible" begin="lineID.mouseover" end="lineID.mouseout" />
</line>

More precisely: I have many line elements and their opacity is changed when mouseover, also visibility of text element is changed when mouse is over any line element, but I don't know how to change child data of text element to show id of the line (over witch cursor is) instead "sometext".

Hi Aleksander. I actually saw your question on StackOverflow and voted it up. I spent a bit of time seeing if I could find an answer, but as far as I can tell it's not possible. I think it's for the same reason that it's not possible (as far as I know) to add new elements without scripting. Sorry about that.

Oh, that was you :). Thanks anyway!

Can you explain or direct me to explanation of why CDATA[ ] is needed?

Hi Steve,

CDATA basically tells the broswer to ignore the data whilst parsing the elements as XML. For more information see: http://www.w3schools.com/xml/xml_cdata.asp

Hi,

Many thanks for the tutorials on SVG. I am a beginner in SVG and Javascript and is working on a project to make an interactive SVG image using JS and DOM. I've worked out the SVG pattern as an 8 petal flower with the ellipse and a circle in center. How do I make it interactive with Javascript?- like for ex: add more petals as indicated by a user through a form? change colours of petals.resize it according to user needs?Colud you please help me?

Thanks in advance

Hi, I've written a brief post explaining how to use Javascript to manipulate an SVG at: http://www.petercollingridge.co.uk/data-visualisation/using-javascript-control-svg. Hope it's of some help.

Hi,

This is really helpfull, but I want to ask how is it possible to diplay multiple information when hovering on the country,for example apart from displaying the name of the country, displaying also the density of the country, the population etc and also do u know if it is possible to visualize the squared power in svg f.i x squared.

Thank you in advance

Peter - great tutorial. I am trying to learn, but have been having issues with mouseover effects on the elements getting "stuck", and this seems to be worse and more common in chrome. Any common pitfalls that I may be running into?

Thanks

 

 

Post new comment

The content of this field is kept private and will not be shown publicly.