Traveling The World in D3 - Part 3: Legend and Filtering

The Knoyd team is currently spread out all over the world (Chile, Colombia and Austria) and I am living on the road for quite a while now. I had an idea to code up an interactive map for our travel blog and thought it would be nice to share with you how to do it yourself.

This is a multi-part tutorial. To get started, you can get all the code from GitHub and also see the final product here

You can also check out Part 1: Making a Map and Part 2: Points and Links.

 

Adding a legend

In the last part of the tutorial, we managed to interactively draw location points on the zoomed in country and added a tooltip linking directly to our content. One thing we are missing is an overview of what content is available for which countries without having to manually click through all the points available.

Firstly lets add a legend to the top level view of the map, listing all the available categories of our content. 

var legend_cont = d3.select("body")
                    .append("div")
                    .attr("class", "legendContainer");


We also need to add some CSS to make it look nicer.

div.legendContainer {
        color: #000000;
        background-color: #fff;
        border-width: 0.1px;
        border-style: solid;
        border-color: #000000;
        padding: .5em;
        border-radius: 5px;
        opacity: 0.9;
        position: absolute;
        font-family: "Lato", sans-serif;
        display: table;
        margin:auto;
        position:absolute;
        top:0;
        bottom:0;
        
    }


Now we have a little frame but it will not help us much without some data in it. We will get a variable containing all the categories and add it as elements of unordered list. Lets start by just listing the names of the categories.

//create unordered list
var legend  = d3.select(".legendContainer")
                .append("ul")
                .attr("class", "legend");

//add list item for every category
var legend_items = legend.selectAll("li")
                     .data(post_cats)
                     .enter()
                     .append("li")
                     .html(function(d, i){return d.name});
 
So far it looks pretty ugly

So far it looks pretty ugly

 

 

We can apply some CSS to make it look much nicer:

ul.legend {
    list-style-type: none;
    list-style: none;
    padding: 3px;
    vertical-align: middle;
    display: block;
    line-height: 50px;
    font-family: "Lato", sans-serif;
    cursor:pointer;
}

Even cooler would be if we could display an icon instead of just simply the name. Well, we can. First define a little helper function to help us assemble the necessary HTML for each of the icons.

// get legend items
function getLegend(d){
    var temp = "<img class='legend_icon' title='ICON_TITLE' \
         src='ICON_LINK' alt='' width='50' height='50'> \
         ICON_KIND";
    temp = temp.replace("ICON_TITLE", d.name);
    temp = temp.replace("ICON_LINK", d.url);
    temp = temp.replace("ICON_KIND", d.name);
    return(temp);
};  

 

Then we just have to apply this function in our HTML call while creating the legend list items.

 

var legend_items = legend.selectAll("li")
                     ...
                     //interesting part
                     .html(function(d, i){return getLegend(d);});

 

With the last little bit of CSS, we make the icons aligned with the name of the category.

 

ul.legend img {
    vertical-align: middle;
    
}
Static legend on a map

Static legend on a map

We now have a decent looking legend on our map. It is, however a little boring if it's just sitting there. In the next section, we are going to make it do something more interesting.

 

Filtering the content

A nice straight forward idea to make the legend more interactive is to use it to filter our blog content on interaction. Once we click on one of the items in the legend, we would like to color differently only the countries that contain blog posts in this category. Lets get to it.

First we make a little helper function to help us color individual country, very similar to the one we used in Part 1 of our tutorial.

// color country according to legend
function colorCountryLegend(country, active_countries) {
    if (active_countries.includes(country.id)) {
        return '#f56260'; //color for selected item
    } else if (visited_countries.includes(country.id)) {
        return '#c8b98d'; //same as in Part 1
    } else {
        return '#e7d8ad'; //same as in Part 1
    }
};


Next we need a helper function to help us figure out, if a particular country has a piece of content in the kind category that we have clicked on.
 

function hasContent(s, kind){
    var post_keys = Object.keys(s.posts);
    return post_keys.includes(kind)
};


Finally we put it all together in one function running the entire process on each click. It selects the countries at hand and colors them using the functions above. (For the data structures used, check the files in the repository)
 

// color countries for particular legend item
function colorCountriesCategory(d){
    //filter relevant countries using hasContent function
    var these_countries = trip_data.filter(function(s){
                                                return hasContent(s, d.name);
                                            });
    var active_countries =  these_countries.map(function(a) {return a.country;});
    var unique = active_countries.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
    
    //color countries 
    g.selectAll('path')
     .attr('fill', function(t){
        return colorCountryLegend(t, unique);
     });
     
};


All we need to do now is triggering the function by clicking on an icon. We can do this simply by adding the action to the li elements.
 

var legend_items = legend.selectAll("li")
                     ...
                    //here
                     .on("click", colorCountriesCategory)
                     .html(function(d, i){return getLegend(d);});


That's it. There are some little things to take care of with discoloring the countries once you decide to zoom in etc., but these are details left as an exercise for you.
 

Colored by the legend

Colored by the legend

Conclusion

In this short tutorial series, we have learned basics of plotting geographical data using the D3.js library. There are plenty of really cool things you can do with D3 and we have only scratched the surface. If you would like to see something else, please write a comment.

In case you are interested in getting deeper knowledge in Data Science, feel free to check our Data Science Bootcamp BaseCamp and sign up :-)