This interactive graph, using data from the World Bank's Global Migration Database shows links between migrants' locations and their countries of birth. The slider bar allows for different cut-off levels for the graph, and the year is selected by scrolling over it.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.link {
stroke: #ccc;
stroke-width: 1.5px;
stroke-opacity: 0.5;
}
.node ellipse {
fill: #fff;
stroke: #000;
stroke-width: 0.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.axis {
opacity: 0.5;
font: 10px sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.axis .domain {
fill: none;
stroke: #000;
stroke-opacity: .3;
stroke-width: 4px;
stroke-linecap: round;
}
.axis .halo {
fill: none;
stroke: #ddd;
stroke-width: 3px;
stroke-linecap: round;
}
.slider .handle {
fill: #fff;
stroke: #000;
stroke-opacity: .5;
stroke-width: 1.25px;
cursor: grab;
}
</style>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 750;
var height = 350;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g").attr("class","linkg");
svg.append("g").attr("class","nodeg");
var force = d3.layout.force()
.size([width - 100, height])
.nodes([{}])
.gravity(0.2)
.charge(-200);
var x = d3.scale.linear()
.domain([500000, 5000000])
.range([250, 80])
.clamp(true);
var brush = d3.svg.brush()
.y(x)
.extent([0, 0]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + (width - 20) + ",0)")
.call(d3.svg.axis()
.scale(x)
.orient("left")
.tickFormat(function(d) { return d / 1000000.0 ; })
.tickSize(0)
.tickPadding(12))
.select(".domain")
.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "halo");
var slider = svg.append("g")
.attr("class", "slider")
.call(brush);
slider.selectAll(".extent,.resize")
.remove();
var handle = slider.append("circle")
.attr("class", "handle")
.attr("transform", "translate(" + (width - 20) + ",0)")
.attr("r", 5);
svg.append("text")
.attr("x", width - 15)
.attr("y", height - 75)
.attr("text-anchor", "end")
.attr("font-size", "10px")
.style("opacity", 0.5)
.text("> x million")
svg.append("text")
.attr("x", width - 15)
.attr("y", height - 60)
.attr("text-anchor", "end")
.attr("font-size", "10px")
.style("opacity", 0.5)
.text("bilateral migration")
var bigGraph = {"nodes":[], "links":[]};
function compare(a,b) {
if (a.i < b.i)
return -1;
if (a.i > b.i)
return 1;
return 0;
}
var selectyeartext = ["2000","1990","1980","1970","1960"];
var selectyearids = [2000,1990,1980,1970,1960];
var selectyear = 2000;
d3.csv("migration.csv", function(error, migrationTable) {
d3.json("countrynames.json", function(error, countrynames) {
// construct graph object from table
var nodes = {};
var nCount = 0;
var lCount = 0;
migrationTable.forEach(function(m){
if (!(m.cCode1 in nodes)) { nodes[m.cCode1] = nCount; nCount += 1; };
if (!(m.cCode2 in nodes)) { nodes[m.cCode2] = nCount; nCount += 1; };
bigGraph.links.push({"source": nodes[m.cCode1] ,
"target": nodes[m.cCode2] ,
"1960": m.totalMigration1960 ,
"1970": m.totalMigration1970 ,
"1980": m.totalMigration1980 ,
"1990": m.totalMigration1990 ,
"2000": m.totalMigration2000 ,
"linkname" : lCount
});
lCount += 1;
});
bigGraph.nodes = $.map(nodes, function(v, k) {
return {"x": width * Math.random(), "y": height * Math.random(), "i": v, "name": k, "countryname": countrynames[k]};
});
bigGraph.nodes.sort(compare);
// text fields used as selectors
for (var i=0; i<selectyearids.length; i++) {
svg.append("text").attr("x", width - 215 + (selectyearids.length - i - 1) * 50).attr("y", height - 10)
.attr("class", "text-button1")
.attr("id", selectyearids[i]).style("opacity", i == 0 ? 1 : 0.5 )
.attr("iv", i)
.attr("text-anchor", "end")
.attr("font-size", "10px")
.text(selectyeartext[i])
.on('mouseover', function(e){
// determine which text-'button' is selected
var selectedNum = parseInt(d3.select(this)[0][0].attributes.iv.value);
// change opacities of text-'buttons'
d3.selectAll(".text-button1").style("opacity",0.5);
d3.select(this).style("opacity",1);
selectyear = selectyearids[selectedNum];
redraw(bigGraph, selectcutoff, selectyear);
});
}
brush.on("brush", brushed);
slider
.call(brush.extent([2000000, 2000000]))
.call(brush.event);
redraw(bigGraph,selectcutoff,selectyear);
});
});
function brushed() {
var value = brush.extent()[0];
if (d3.event.sourceEvent) { // not a programmatic event
value = x.invert(d3.mouse(this)[1]);
brush.extent([value, value]);
}
handle.attr("cy", x(value));
selectcutoff = value;
redraw(bigGraph,selectcutoff,selectyear);
}
function redraw(bigGraph, cutoff, year) {
var currentNodes = force.nodes();
var graph = {"nodes":[], "links":[]};
var nodeList = {};
var nlCount = 0;
graph.links = $.map(bigGraph.links, function(v) {
if (v[''+year] > cutoff) {
if (!(v.source in nodeList)) { nodeList[v.source] = nlCount; nlCount += 1; };
if (!(v.target in nodeList)) { nodeList[v.target] = nlCount; nlCount += 1; };
return {"source": nodeList[v.source], "target": nodeList[v.target], "distance": v[''+year], "linkname": v.linkname};
}
});
graph.nodes = $.map(bigGraph.nodes, function(v) {
if (v.i in nodeList) {
var current = -1;
currentNodes.forEach(function(n,i){
if (n.name == v.name) { current = i; }
});
if (current > -1) {
return {"x": currentNodes[current].x, "y": currentNodes[current].y, "i": nodeList[v.i], "name": v.name, "countryname": v.countryname};
} else {
return {"x": v.x, "y": v.y, "i": nodeList[v.i], "name": v.name, "countryname": v.countryname};
}
}
});
graph.nodes.sort(compare);
force
.nodes(graph.nodes)
.links(graph.links);
var link = svg.selectAll(".linkg").selectAll(".link").data(graph.links,function(v){ return v.linkname; });
link.enter()
.append("line")
.attr("class", "link")
.style("opacity",1e-6)
.transition()
.duration(1500)
.style("opacity",1);
link.exit()
.transition()
.duration(500)
.style("opacity",1e-6)
.remove();
var node = svg.selectAll(".nodeg").selectAll(".node").data(graph.nodes,function(v){ return v.name; });
node.exit()
.transition()
.duration(500)
.style("opacity",1e-6)
.remove();
var node2 = node.enter()
.append("g")
.attr("class", "node");
node2.append("ellipse")
.attr("cx", 0)
.attr("cy", 0)
.attr("rx", 16)
.attr("ry", 8)
.attr("title", function(d) { return d.countryname })
.style("opacity",1e-6)
.transition()
.duration(1500)
.style("opacity",1);
node2.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) { return d.name })
.style("opacity",1e-6)
.transition()
.duration(1500)
.style("opacity",1);
force
.linkDistance(function(l) { return 20 + 5 * Math.log(1.0 + 1000000000 / l.distance); })
.start();
force.on("tick", function() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
}
</script>
</body>
</html>
Modified http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js to a secure url
Modified http://d3js.org/d3.v3.min.js to a secure url
https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
https://d3js.org/d3.v3.min.js