Alberta A lake surrounded by mountains

Divided from British Columbia by the Rocky Mountains, over which gusts of wind, known as chinooks, keep the southern part of the province considerably warmer than its eastern neighbours during the winter.

British Columbia Photograph of a forested coastline, with mountains beyond

British Columbia is part of the Cascadia region, bounded by the Pacific Ocean on the west and the Rocky Mountains to the east. The environment ranges from temperate rain forest to extremely cold in the north.

Manitoba

Manitoba is sparsely populated, especially in the north, which is dominated by arctic tundra and the Canadian Shield, a vast area of igneous rock that forms the ancient geological core of the North American continent.

New Brunswick White lighthouse decorated with a red cross photographed in front of a fog bank

One of Canada’s three Maritime provinces, bordering the Atlantic ocean. It is the only constitutionally bilingual province, partly due to the presence of the large Acadian population.

Newfoundland & Labrador Scattered houses on a bluff above the ocean

The area has a deep history, being inhabited by the Beothuk, Inuit, Mi'kmaq and Innu for thousands of years. European content began in 1001 CE, when the area was temporarily settled by Vikings.

Nova Scotia A lighthouse at dusk, shot from below, with rocks in the foreground

Canada’s second-smallest province is almost completely surrounded by water. It’s unique geographical history has also made it a valuable location for fossils, mostly from the Carboniferous, Triassic and Jurassic periods.

Northwest Territories Rust-red houseboats on a blue river

Part of Canada since 1870, the Northwest Territories were subdivided in 1999 to form the territory of Nunavut, gaining the portion that has a slightly warmer climate and taiga forest.

Nunavut Yellow buildings against a frozen ocean

The largest Canadian territory, with a land area equivalent to Western Europe. Also the least populous, with just over 30,000 permanent citizens. Nunavut also has the northernmost permanent habitat in the world in the form of the Alert scientific outpost.

Ontario Old buildings of Parliament Hill reflected in a curve of the Ottawa river during fall

Canada’s most populous province, with nearly 40% of the population, much of it clustered around Lake Ontario and in the city of Toronto, Canada’s largest city. Ontario also holds the nation’s capital of Ottawa.

Prince Edward Island A beach boardwalk through pink grass

The smallest province in both land area and population, Prince Edward Island grows 25% of the nation's potatoes.

Quebec Photograph of Quebec city showing old and modern buildings against a lake, behind a green hill

Quebec is Canada’s second-largest province, and the only one to use French as its sole official language. Independence movements have played a significant role in the history and politics of the province.

Saskatchewan Old weathered grain silos against a blue sky

Saskatchewan's location near the geographical center of the country creates an environment with the greatest range of temperature in Canada, from 40°C in the summer to -40°C in winter.

Yukon A range of rocky snowcapped mountains, partly hidden by cloud

The Yukon escaped glaciation during the last Ice Age, making it a welcome home for the First Nations. Today ¾ of the population lives in the territory’s only city of Whitehorse; the second-largest habitation has just 2000 people.

In previous articles I’ve demonstrated how SVG can be used to create responsive, scalable imagemaps, but have left the interactive part - aside from simple hover effects - largely unexplored, with a few exceptions. That changes with this article and the one following, which explore how to create a full, in-depth interface using and : in this case, a geographical map.

Sourcing The Map

One of the pleasures of using SVG today is the fact that a decade of relative obscurity has provided the opportunity for a long period of quiet professional content development, much of it free and/or open source. For example, every country is available as an SVG drawing, together with most geographical regions. I’ll use Wikipedia’s SVG map of Canada as a source for this interactive.

Cleaning up the source

This is not to say that every SVG drawing is exactly how you need it; many are over-coded, and most need to be cleaned up to some degree. In the case of Wikipedia’s map, I did the following:

  1. Used Adobe Illustrator to straighten the map and eliminate territory in the northernmost provinces (sorry, Nunavut!) in order to save space.
  2. I also used Illustrator’s Simply tool to reduce the number of points in the SVG, minimizing file size and clutter. Using Preview mode while doing this is strongly recommended, as too high a value will make the map appear somewhat “melted”.
    Reducing the vector map to ~85% precision is a good balance between accuracy and file size
  3. I also renamed the id attributes of each layer; these become the values associated with each group or path.

After export, the was further cleaned up:

  1. The opening SVG tag becomes:
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
    viewBox="0 0 1386 822" id="canada">
  2. Each province or territory is surrounded by a group, if one doesn’t already exist; the group has the appropriate id.
    <g id="Alberta">
            <path d="M435.3,670.2c-13.1-1.1-44…" >
    </g>
  3. A stylesheet is added to the SVG:
    #canada { 
        fill: hsl(120, 98%, 30%);
    }
    #canada g {
        transition: .3s;
    }
    #canada g:hover { 
        fill: hsl(120, 98%, 60%);
        cursor: pointer;
    }
  4. A <title> tag is added inside each group for accessibility:
    <g id="Alberta">
            <title>Alberta</title>
            <path d="M435.3,670.2c-13.1-1.1-44…" >
    </g>

(The title text will also appear on mouse rollover for all users).

Adding Info

I’ve added extra information about each territory inside a <desc> element for each group. <desc> content is not rendered on the page, as it is traditionally reserved for accessibility. For Alberta, the added markup looks like this:

<desc>
<image xlink:href="moraine_lake.jpg" alt="A lake surrounded by mountains">
</image>
<p>One of Canada"s four prairie provinces, and
the most populous, Alberta borders a single US 
state (Montana). It is divided from British 
Columbia by the Rocky Mountains…</p>
</desc>

The entire SVG code is embedded into an HTML page, with a <div> added above it:

<div id="provinceInfo"></div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1386 822" id="canada">

Adding Style

The initial CSS for the map is as follows:

* { box-sizing: border-box; }
body {
    background: #333;
    color: #fff;
    font-family: Avenir, Calibri, sans-serif;
}
#canada-map { 
    fill: hsl(120, 55%, 20%);
}
#canada-map g {
    transition: .3s;
}
#canada-map g:hover { 
    fill: hsl(120, 55%, 40%);
    cursor: pointer;
}

HSL is used to create easy color shifts; fill is applied the SVG root element so that the paths inside will inherit the color. Much the same philosophy is used to affect the paths through the g element (since some provinces and territories consist of more than one path, a hover style applied to a path element would affect only that path; applied to the group element, all paths inside the group are affected equally.)

The provinceInfo element is currently blank; we’re going to add CSS to style the content that will be applied to it with .

#provinceInfo { 
    position: absolute;
    top: 0; right: 0;
    width: 25%;
    background: rgba(0,0,0,0.3);
    pointer-events: none;
    opacity: 0;
    transition: 1s; 
}
@media all and (max-width: 800px) {
    #provinceInfo { width: 40%; }
}
#provinceInfo h1 {
    background: hsl(240, 55%, 40%);
    padding: .3rem;
    padding-left: 1rem;
    margin-top: -.5rem;
    font-weight: 400;
}
#provinceInfo p {
    margin-left: 2rem;
    margin-right: 2rem;
}
#provinceInfo img {
    width: 100%;
}

There’s a little more styling to add, but for now let’s turn to a script added to the bottom of the page:

Adding Interaction

var canadamap = document.getElementById("canada-map"),
provinceInfo = document.getElementById("provinceInfo"),
allProvinces = canadamap.querySelectorAll("g");
canadamap.addEventListener("click", function(e){ 
    var province = e.target.parentNode;
    if (e.target.nodeName == "path") {
        for (var i=0; i < allProvinces.length; i++) {
            allProvinces[i].classList.remove("active");
        }
        province.classList.add("active");
        var provinceName = province.querySelector("title").innerHTML,
        provincePara = province.querySelector("desc p");
        sourceImg = province.querySelector("img"),
        imgPath = "";
        provinceInfo.innerHTML = "";
        provinceInfo.insertAdjacentHTML("afterbegin", 
        "<img src="+imgPath + sourceImg.getAttribute("xlink:href")+" 
        alt=""+sourceImg.getAttribute("alt")+""><h1>"+provinceName+
        "</h1><p>"+provincePara.innerHTML+"</p>");
        provinceInfo.classList.add("show");
    }
})

An explanation: after identifying the provinceInfo popup window, all of the provinces and territories are gathered together by finding all the g elements. When the user clicks inside the SVG, event bubbling is used to determine which path the user clicked on (and to check that the user didn’t click on an area of ocean).

Since SVG doesn’t have a proper :focus state, I’ve used an active class applied to the group to indicate the current province / territory; the same class is removed from all the others.

Searching inside the current group, the title of the group is used to determine the correct province name, the paragraph inside the desc for the description, and the src and alt of the hidden image for the illustrative photograph. imgPath is currently blank, as it is assumed that all files, including the photographs, are in the same location as the SVG.

All of this is concatenated together and written inside a cleared provinceInfo using insertAdjacentHTML. The window is then made visible with a class of show, using a final declaration added to the CSS:

#provinceInfo.show { opacity: 1; }

At this stage, the info window does not have a Close button; to get around this issue, the <div> element has had pointer-events: none added to it, making it “transparent” to mouse actions and allowing the user to click through to paths underneath it.

Photographs by Mariusz Kluzniak, Kent McFarland, Daryl Mitchell, Vincent Lock, Vince Alongi, Timothy Neesam, Paul Bica, KingstonGal and Nicolas Raymond, licensed under Creative Commons.