D3.js: Map of Croatia
Development | Denis Bracun

D3.js: Map of Croatia

Thursday, Aug 10, 2017 • 3 min read
This is the first post in a two part tutorial on how to create a simple map of a country with county view using TopoJSON and D3.js.

Introduction

When you have a bunch of statistical data that has to be presented to end users, you need to find a way how to visualize that data, so it’s not boring and cumbersome to look at. This is where D3.js (Data-Driven Documents) comes into play. With D3.js, we can create interactive and animated data visualizations.

This tutorial will consist of two separate blog posts. In the first one, we will create a simple map of Croatia with a county view using TopoJSON and D3.js. The second blog post will explain how to create a bar chart graph with some tourism statistics taken from MINT, and how to show that in a tooltip when hovering over a certain county.

Let’s begin.

Map

To make things simple, we will start the process of creating a map using GeoJSON file for Croatia and convert it to TopoJSON. If you have Shapefile and you want to convert it to TopoJSON, check out this tutorial on Command-Line Cartography written by Mike Bostock.

To convert GeoJSON to TopoJSON we need to install topojson-server using NPM:

npm install -g topojson-server

and then just call the following command:

geo2topo cro.geojson > croatia.json

If you were wondering about the differences between those two formats, TopoJSON is actually an extension of GeoJSON that encodes topology. TopoJSON eliminates redundancy, allowing related geometries to be stored efficiently. This result in more compact files, often providing a reduction of 80% in file size.

Now let’s start building HTML and CSS for our map. Create an index.html in the same folder where we generated croatia.json using the following template:

<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
    <script src="http://d3js.org/d3.v4.min.js"></script>
    <script src="http://d3js.org/topojson.v2.js"></script>
    <script src="https://d3js.org/d3-queue.v3.min.js"></script>
    <script>

    </script>
</body>

Now we will create SVG for rendering two-dimensional geometry:

var width = 960,
    height = 800;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

The next step is to make a call to d3.json using d3-queue to load the TopoJSON file:

d3.queue()
    .defer(d3.json, "croatia.json")
    .await(ready);

function ready(error, cro) {
    if (error) throw error;
    console.log(cro);
}

When you open your index.html and look at JavaScript console, you should see the topology object.

Now we just need two more things to render geography:

  1. Path generator - takes the projected 2D geometry and formats it appropriately for SVG.

  2. Projection - projects a point from sphere to a plane. In our case, we will be using d3.geoAlbers() projection for displaying the map.

The default position of d3.geoAlbers() is somewhere in the US (around Hutchinson, Kansas), so we need to add parallels, center, rotate, scale and translate. After that, our code should look something like this:

var projection = d3.geoAlbers()
    .parallels([40, 50])
    .center([0, 45])
    .rotate([-16, 0])
    .scale(8000)
    .translate([width / 2, height / 2]);

var path = d3.geoPath().projection(projection);

Although TopoJSON is used to store data more efficiently than GeoJSON, we need to convert that data back to the GeoJSON for display - append a path element to which we will bind the converted data and add a selection.attr that will connect our data with selected elements:

function ready(error, cro) {
    if (error) throw error;
    svg.append("g")
        .attr("class", "land")
        .selectAll("path")
        .data(topojson.feature(cro, cro.objects.cro_regions).features)
        .enter().append("path")
        .attr("d", path)
        .on("mouseover", function (d) { })
        .on("mouseout", function (d) { });
}

Finally, you should see something like this when you refresh index.html:

un-formatted map of croatia

Maybe you noticed mouseover and mouseout events - we will use those for a tooltip a bit later. Now we will add county boundaries and a little formatting through CSS.

For boundaries, we will use topojson.mesh for which we need to pass topology and geometry objects. Also, we will define an optional filter that will reduce the set of returned boundaries, which takes two arguments that represent two features on either side of the boundary.

svg.append("path")
    .datum(topojson.mesh(cro, cro.objects.cro_regions, 
        function (a, b) { return a !== b; }))
    .attr("class", "border")
    .attr("d", path);

And we will add this CSS:

.land {
    fill: #e5e5e5;
}

.land :hover {
    fill: rgba(255, 158, 44, .5);
}

.border {
    fill: none;
    stroke: #fff;
    stroke-linejoin: round;
    stroke-linecap: round;
}

Tooltip

As a next step of building the interactive map, we will add a tooltip that will display the county name when we hover over it.

First, let’s define a tooltip:

var tooltip = d3.select("body").append("div")
			.attr("class", "tooltip")
			.style("opacity", 0)
			.style("width", 600);

Now we can define the text and the position using d3.event.pageX (returns the horizontal coordinate of the event relative to the whole document) and d3.event.pageY (returns the vertical coordinate of the event relative to the whole document)`.

//replace empty vents with this code
.on("mouseover", function (d) {
    var tip = "<h3>" + d.properties.name + "</h3>";
    tooltip.html(tip)
        .style("left", (d3.event.pageX) + "px")
        .style("top", (d3.event.pageY) + "px");

    tooltip.transition()
        .duration(500)
        .style("opacity", .7);

})
.on("mouseout", function (d) {
    tooltip.transition()
        .duration(500)
        .style("opacity", 0);
});

To add tooltip styling:

.tooltip {
   position: absolute;
   max-width: 400px;
   height: auto;
   padding: 5px;
   background-color: white;
   -webkit-border-radius: 5px;
   -moz-border-radius: 5px;
   border-radius: 5px;
   -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
   -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
   box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
   pointer-events: none;
   font-family: sans-serif;
   font-size: 12px;
}

And the final product should look something like this:

finished map of croatia

We are done with map creation, and our next step is to create a chart that will show detailed statistics for each county.

Stay tuned!