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.