A Chord Diagram visualization of global migration data from 2015. Data is from the United Nations International Migrant Stock Database. Inspired by:
forked from curran's block: Global Migration in 2015
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.2/js/tether.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.min.js"></script>
</head>
<body>
<script>
// Configuration parameters.
var width = 960,
height = 960,
outerPadding = 150,
labelPadding = 5,
chordPadding = 0.03,
arcThickness = 30,
opacity = 0.5,
fadedOpacity = 0.01,
transitionDuration = 300,
outerRadius = width / 2 - outerPadding,
innerRadius = outerRadius - arcThickness,
valueFormat = d3.format(",");
// DOM Elements.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
ribbonsG = g.append("g"),
groupsG = g.append("g");
// D3 layouts, shapes and scales.
var ribbon = d3.ribbon()
.radius(innerRadius),
chord = d3.chord()
.padAngle(chordPadding)
.sortSubgroups(d3.descending),
arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius),
color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
var popoverOptions = {
html : true,
template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><div class="popover-content"></div></div>'
};
// Renders the given data as a chord diagram.
function render(data){
var matrix = generateMatrix(data),
chords = chord(matrix);
color.domain(matrix.map(function (d, i){
return i;
}));
// Render the ribbons.
ribbonsG.selectAll("path")
.data(chords)
.enter().append("path")
.attr("class", "ribbon")
.attr("d", ribbon)
.style("fill", function(d) {
return color(d.source.index);
})
.style("stroke", function(d) {
return d3.rgb(color(d.source.index)).darker();
})
.style("opacity", opacity)
.on("mouseenter", function(d){
var src = matrix.names[d.source.index];
var dest = matrix.names[d.target.index];
popoverOptions.content = [
"<strong>" + src +" to " + dest +"</strong>",
valueFormat(d.target.value),
"<br><strong>" + dest +" to " + src +"</strong>",
valueFormat(d.source.value)
].join("<br>");
$(this).popover(popoverOptions);
$(this).popover("show");
})
.on("mouseleave", function (d){
$(this).popover("hide");
})
// Scaffold the chord groups.
var groups = groupsG
.selectAll("g")
.data(chords.groups)
.enter().append("g");
// Render the chord group arcs.
groups
.append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(group) {
return color(group.index);
})
.style("stroke", function(group) {
return d3.rgb(color(group.index)).darker();
})
.style("opacity", opacity)
.call(groupHover);
// Render the chord group labels.
var angle = d3.local(),
flip = d3.local();
groups
.append("text")
.each(function(d) {
angle.set(this, (d.startAngle + d.endAngle) / 2)
flip.set(this, angle.get(this) > Math.PI);
})
.attr("transform", function(d) {
return [
"rotate(" + (angle.get(this) / Math.PI * 180 - 90) + ")",
"translate(" + (outerRadius + labelPadding) + ")",
flip.get(this) ? "rotate(180)" : ""
].join("");
})
.attr("text-anchor", function(d) {
return flip.get(this) ? "end" : "start";
})
.text(function(d) {
return matrix.names[d.index];
})
.attr("alignment-baseline", "central")
.style("font-family", '"Helvetica Neue", Helvetica')
.style("font-size", "10pt")
.style("cursor", "default")
.call(groupHover);
}
// Sets up hover interaction to highlight a chord group.
// Used for both the arcs and the text labels.
function groupHover(selection){
selection
.on("mouseover", function (group){
g.selectAll(".ribbon")
.filter(function(ribbon) {
return (
(ribbon.source.index !== group.index) &&
(ribbon.target.index !== group.index)
);
})
.transition().duration(transitionDuration)
.style("opacity", fadedOpacity);
})
.on("mouseout", function (){
g.selectAll(".ribbon")
.transition().duration(transitionDuration)
.style("opacity", opacity);
});
}
// Generates a matrix (2D array) from the given data, which is expected to
// have fields {origin, destination, count}. The matrix data structure is required
// for use with the D3 Chord layout.
function generateMatrix(data){
var nameToIndex = {},
names = [],
matrix = [],
n = 0, i, j;
function recordName(name){
if( !(name in nameToIndex) ){
nameToIndex[name] = n++;
names.push(name);
}
}
data.forEach(function (d){
recordName(d.origin);
recordName(d.destination);
});
for(i = 0; i < n; i++){
matrix.push([]);
for(j = 0; j < n; j++){
matrix[i].push(0);
}
}
data.forEach(function (d){
i = nameToIndex[d.origin];
j = nameToIndex[d.destination];
matrix[j][i] = d.count;
});
matrix.names = names;
return matrix;
}
d3.csv("migration.csv", type, function (dataForCountries){
d3.json("hierarchy.json", function (hierarchy){
// Aggretate country data into regional data.
var dataForRegions = aggregate(dataForCountries, hierarchy)
// Reduce clutter by filtering out links with relatively low counts.
.filter(function (d){
return d.count > 10000;
});
render(dataForRegions);
});
});
// Parses a single row of the input table.
function type(d){
d.count = +d.count;
return d;
}
// Aggregates data from countries to regions.
function aggregate(data, hierarchy){
var links = {},
parent = {},
descendants = d3.hierarchy(hierarchy).descendants();
descendants.forEach(function (node){
if(node.parent){
parent[node.data.data.id] = node.parent.data.data.id;
}
});
function getLink(origin, destination){
var key = origin + "|" + destination;
return (key in links) ? links[key] : (links[key] = {
origin: origin,
destination: destination,
count: 0
});
}
data.forEach(function (d){
getLink(parent[d.origin], parent[d.destination]).count += d.count;
//console.log(d.origin + " is in " + parent[d.origin]);
//console.log(d.destination + " is in " + parent[d.destination]);
});
return Object.keys(links).map(function (key){
return links[key];
});
}
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.2/js/tether.min.js
https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.min.js