Layouts / Maps & Networks
Now that I've covered the basics of programming in D3, let's take a look at some of the other cool things one can build with D3. Before jumping into the code, it's worth mentioning the resources available within the D3 community for sharing reusable code. As of writing this there are sites like VisHub or the D3 Graph Gallery which contain some commonly used D3 code snippets.
Also, this guide mostly follows version 7 as it is the latest . Sometimes libraries are slow to update documentation examples so it is important to read the change log when trying to upgrade D3.
Layouts
Layout functions format the data to include attributes that allow for drawing the data.
I found the below visualizations here and here, but note it uses D3 v3 syntax, for v4+ we would just use d3.pie() rather than d3.layout.pie()
Other layouts include:
- d3.force() - Nodes are represented as circles and edges (connections) as lines
- d3.stack() - Good for illustrating how ratios compare over time
Choosing the Right Data Structure
There are cases where the above functions expect data in a certain format, that being either an Array or Object (map).
The advantage with Maps is we can find a key without the index or searching the entire data structure. In D3 we often work with Arrays of objects, which is good for looping over objects but can make searching for a field within an object cumbersome. The D3 group(), or groups() function (previously known as nest()) can create a map of JS objects based on a category.
Maps and Networks
The first step towards creating a visualization of a region is finding the map file to use, containing information about borders and locations. There several places online that can create files for this purpose, the most popular format being GeoJSON and D3.geoPath(). Another format is TopoJSON which also encodes topology, and are usually smaller than GeoJSON even though they include more data.
When building maps converting a sphere to a 2D surface can be problematic. To get the map to fit nicely to the screen we often apply some kind of projection that stretches the areas nearer to the poles, making them appear larger than they actually are. In D3 we have the flexibility to choose the projection we want to use with our maps.
var width = 600,
height = 400;
var projection = d3.geoConicEqualArea()
.scale(153) // How much to zoom in
.translate([width / 2, height / 2]) // Center the map
.precision(.1);
var path = d3.geoPath()
.projection(projection); // Define the path and attach projection
var graticule = d3.geoGraticule(); // The coordinate lines on the map
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("data/world-110m.json").then(function(world) {
svg.append("path")
// Land borders for the map
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.append("path")
// Define borders between countries
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
});
d3.select(self.frameElement).style("height", height + "px");
So in the above I use d3.geoConicEqualArea() to get a cone shaped map with actual sizes, the typical projection of the standard classroom world map would be geoMercator() instead.
Loading Multiple Datasets
So now we know how to load map data, but we'll need to load another file with regional information to create useful visualizations. To do this we will learn about Queues (D3 < v5) and Promises (D3 v5+).
Let's say we have a JSON and a csv to load. We might be tempted to write two different data statements or nest them together. Here's the issue:
// METHOD 1 - With a global variable (WRONG)
var mapData, infoData; // Global variables
// Async data loading for json
d3.json("map.json", function(map){
mapData = map;
})
// Async data loading for csv
d3.csv("info.csv", function(info){
infoData = info;
})
// Code here to do something with the data
// ISSUE: This gets run immediately even if the above datasets are not initialized
// METHOD 2 - Nested data statements (WRONG)
d3.json("map.json", function(map){
d3.csv("info.csv", function(info){
// Code here to do something with the data
})
})
// ISSUE: This works, but takes a long time because the page can't start loading
// the second file before the first is done