SVG scripting example: an interactive map


5 Dec 2010 Code on Github

Introduction

The previous tutorial covered the basics of using Javascript to add interactivity to an SVG. This is a larger, more complex, more realistic, but more specific example. For an introduction to how to create SVGs in general please take a look at my SVG tutorial.

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, although it's a little 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 find this map by clicking the Github link at the top of the page.

Step 3. Display country names

The first script effect I'll demonstrate is how to change the text in a text element. 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.

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 that changes the text value. I'm adding this function inside a script element within the SVG itself (at the end so it loads after the text element), but you can put the code in a separate JS file if you prefer. I've written about how to that will affect the code here.

<script type="text/javascript"><![CDATA[
function displayName(name) {
    document.getElementById('country-name').firstChild.data = name;
}
]]></script>

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.

<path id="algeria" onmouseover="displayName('Algeria')" d="..." />

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. If you're clever you could make a JS function to find all the country paths, but somewhere you're going to have to figure out what each path is called (you could store the information in an attribute on the path or extract it from the id, but then you still have to go through each path adding the country name data).

However you do it, you should end up with a map like this:

image/svg+xml Blank map Peter Collingridge

Step 4. 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:

var country_id = 'libya';
var colour = '#004400';
var country = document.getElementById(country_id);
country.setAttributeNS(null, '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.

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

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 = document.getElementById(name);
    country.className += ' colour' + colour;
}

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

colourCountry('algeria', 2)

Step 5. 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 intuitive data structure, it is easy to program (it's also how the data I had was arranged - if your data is arranged differently a different approach might work better).

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);
    }
  }
}

This is how my map ended up looking. You can find the code by clicking the Github link at the top of the page or by right clicking the image and selecting View Frame Source.

Comments (36)

Da McCreary on 15 Mar 2011, 7:01 p.m.

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

Thanks! - Dan

Peter on 1 Apr 2011, 12:15 p.m.

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.

alaleman on 9 Aug 2011, 8:32 a.m.

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

Ichi on 23 Sep 2011, 7:18 p.m.

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

Peter on 23 Sep 2011, 7:56 p.m.

Well, I would use a tooltip (tutorial at http://petercollingridge.co.uk/tutorials/svg/interactive/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

Ichi on 26 Sep 2011, 3:03 p.m.

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

Haya on 18 Mar 2012, 10:44 p.m.

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

Arulmurugan on 14 Jul 2012, 6:47 a.m.

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.

Peter on 14 Jul 2012, 1:13 p.m.

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: http://www.webmapper.net/svg/serve/

Eric on 27 Jul 2012, 2:04 p.m.

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

Peter on 27 Jul 2012, 2:59 p.m.

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

Eric on 28 Jul 2012, 5:56 a.m.

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.

Migayi on 4 Aug 2012, 5:24 p.m.

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

Peter on 4 Aug 2012, 9:14 p.m.

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.

ximu on 9 Nov 2012, 4:04 p.m.

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?

Peter on 9 Nov 2012, 4:34 p.m.

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.

ximu on 12 Nov 2012, 10:31 a.m.

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!

Peter on 13 Nov 2012, 11:44 a.m.

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.

Paul Whipp on 13 Jan 2013, 12:28 a.m.

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).

Aleksandar on 29 Jan 2013, 5:48 p.m.

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".

Peter on 30 Jan 2013, 12:10 a.m.

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.

Aleksandar on 30 Jan 2013, 2:51 p.m.

Oh, that was you :). Thanks anyway!

Steve on 6 Mar 2013, 11 p.m.

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

Peter on 7 Mar 2013, 1:39 p.m.

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

Anonymous on 19 Mar 2013, 11:13 p.m.

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

Peter on 29 Mar 2013, 5:33 p.m.

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

kostas on 1 Oct 2013, 9:09 p.m.

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

Mike on 22 Sep 2014, 2:30 p.m.

Hello,

first - thanks for this impressive and well documented tutorial.

i tried it out - it works perfect.
the only thing i'm not getting to work is, handling multi line mouseover Textes.

How would you handle that?

Thanks in advice,
regards
Mike

Jordan on 8 Nov 2014, 10:34 p.m.

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

JTV on 8 Sep 2015, 6:07 p.m.

Dear Peter,

despite are these articles quite older they taught me the basics of svg scripting. Thanks a lot!

Thanks to it I was able to find an error in step 3. Inside the if of the onload function there is a bad object name. It should be

function init(evt) {
if ( window.svgDocument == null ) {
svgDocument = evt.target.ownerDocument;
}
}

Hope this will help to next diggers :-)

RobertTurner on 14 Dec 2015, 6:16 a.m.

Thanks for the tutorial .I have read your blog and it will help me a lot.

Mary Jane Rutkowski on 2 Sep 2016, 8:22 p.m.

Hi Peter
Thank you for your excellent tutorials! I am using your basic layout to illustrate counties in Maryland. I would also like to add a table that would be populated with additional information from the svg ... data={"County": "Talbot", "FIPS": "41", ...}
Have you done this? or would you be able to point me in the right direction? Thanks :-)

Anthony on 13 Oct 2016, 4:57 a.m.

Hi Peter,

Sorry for the newbie question here :-) Is there anyway I can insert a carriage return or <br> in the country name variable?

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

Thanks in advance Peter!
-aNt

Michael Pfaff on 12 Aug 2017, 10:19 p.m.

Hi Peter!

Awesome tutorial! I have it all setup and working like a gem. However, I want to have multiple pieces of information to display -- country name, ruler, etc.

How can I create multiple data bits on each path and display them all?

I'm thinking: country_name, ruler, X, Y, Z bits of data.

Can I just create a new function for each and have multiple effects onmouseover?

https://nerdlouisville.org/shanri/

Here's my map:

Thanks!

Mike

David on 19 Jan 2019, 1:22 a.m.

I'm trying to figure out how to drag a box around several continents and select them using svg in Angular 7.
Thanks for any help.

Sean on 9 Mar 2020, 4:34 a.m.

Hi Peter,

Good tutorial! I am working on a project that I need to allow users to add markers to their maps on the front end. Do you know of a good way to go about doing this using MapSVG?