This Refugee Flow visualization showws the flow patterns of refugee from one country to others with an path element arc to represent the flow path. The VisDock toolkit has been integrated into this Briesemeister example built with D3.js (found here) by Ilya Boyandin. Using selection and other toolkits, users can query countries and the corresponding refugee flow into the countries. Pan & Zoom and Rotate can come in handy for a complicated visualization like this one. For more information about VisDock, please cick on the link.
xxxxxxxxxx
<meta charset="utf-8">
<html>
<head>
<link href="https://rawgithub.com/VisDockHub/NewVisDock/master/master/visdock.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3@2.10.3/d3.v2.js"></script>
<script src="https://rawgithub.com/visdockhub/newvisdock/master/master/visdock.js"></script>
<script src="https://rawgithub.com/visdockhub/newvisdock/master/master/2d.js"></script>
<script src="https://rawgithub.com/visdockhub/newvisdock/master/master/intersectionutilities.js"></script>
<script src="https://rawgithub.com/visdockhub/newvisdock/master/master/visdock.utils.js"></script>
<style type="text/css">
svg {
border:1px solid gray
}
#countries path {
fill: #f5f5f5;
stroke: #fff;
}
#arcs path {
fill: none;
stroke-opacity: .9;
}
</style>
</head>
<body>
<script type="text/javascript">
var w = 960,
h = 500;
var useGreatCircles = true;
VisDock.init("body", {width: 1200, height: 800});
var viewport = VisDock.getViewport();
d3.loadData = function() {
var loadedCallback = null;
var toload = {};
var data = {};
var loaded = function(name, d) {
delete toload[name];
data[name] = d;
return notifyIfAll();
};
var notifyIfAll = function() {
if ((loadedCallback != null) && d3.keys(toload).length === 0) {
loadedCallback(data);
}
};
var loader = {
json: function(name, url) {
toload[name] = url;
d3.json(url, function(d) {
return loaded(name, d);
});
return loader;
},
csv: function(name, url) {
toload[name] = url;
d3.csv(url, function(d) {
return loaded(name, d);
});
return loader;
},
onload: function(callback) {
loadedCallback = callback;
notifyIfAll();
}
};
return loader;
};
var projection = d3.geo.mercator()
.translate([480, 300])
.scale(970);
var flow_links = [];
var country_array = [];
var country_index = {};
var node_array = [];
var choices = [];
var queries = [];
var path = d3.geo.path()
.projection(projection);
var arc = d3.geo.greatArc().precision(3)
var svg = viewport;
var countries = svg.append("g").attr("id", "countries");
var centroids = svg.append("g").attr("id", "centroids");
var arcs = svg.append("g").attr("id", "arcs");
svg.append("text")
.attr("id", "loading")
.attr("x", 5)
.attr("y", 17)
.attr("font-size", "9pt")
.attr("font-family", "arial")
.text("Loading...");
d3.loadData()
.json('countries', 'world-countries.json')
.csv('nodes', 'refugee-nodes.csv')
.csv('flows', 'refugee-flows.csv')
.onload(function(data) {
d3.select("#loading").attr("visibility", "hidden");
var nodeDataByCode = {}, links = [];
var year = '2008';
var maxMagnitude =
d3.max(data.flows, function(d) { return parseFloat(d[year])});
var magnitudeFormat = d3.format(",.0f");
var arcWidth = d3.scale.linear().domain([1, maxMagnitude]).range([.1, 7]);
var minColor = '#f0f0f0', maxColor = 'rgb(8, 48, 107)';
var arcColor = d3.scale.log().domain([1, maxMagnitude]).range([minColor, maxColor]);
var arcOpacity = d3.scale.log().domain([1, maxMagnitude]).range([0.3, 1]);
countries.selectAll("path")
.data(data.countries.features)
.enter().append("path")
.attr("d", path);
function nodeCoords(node) {
var lon = parseFloat(node.Lon), lat = parseFloat(node.Lat);
if (isNaN(lon) || isNaN(lat)) return null;
return [lon, lat];
}
data.nodes.forEach(function(node) {
node.coords = nodeCoords(node);
node.projection = node.coords ? projection(node.coords) : undefined;
nodeDataByCode[node.Code] = node;
});
data.flows.forEach(function(flow) {
var o = nodeDataByCode[flow.Origin], co = o.coords, po = o.projection;
var d = nodeDataByCode[flow.Dest], cd = d.coords, pd = d.projection;
var magnitude = parseFloat(flow[year]);
if (co && cd && !isNaN(magnitude)) {
links.push({
source: co, target: cd,
magnitude: magnitude,
origin:o, dest:d,
originp: po, destp:pd
});
}
});
flow_links = links;
centroids.selectAll("circle")
.data(data.nodes.filter(function(node) { return node.projection ? true : false }))
.enter().append("circle")
.attr("cx", function(d) { return d.projection[0] } )
.attr("cy", function(d) { return d.projection[1] } )
.attr("r", 1)
.attr("fill", "#000")
.attr("opacity", 0.5)
;
var strokeFun = function(d) { return arcColor(d.magnitude); };
function splitPath(path) {
var avgd = 0, i, d;
var c, pc, dx, dy;
var points = path.split("L");
if (points.length < 2) return path;
var newpath = [ points[0] ];
var coords = points.map(function(d, i) {
return d.substr(i > 0 ? 0 : 1).split(","); // remove M and split
});
// calc avg dist between points
for (i = 1; i < coords.length; i++) {
pc = coords[i-1]; c = coords[i];
dx = c[0] - pc[0]; dy = c[1] - pc[1];
d = Math.sqrt(dx*dx + dy*dy);
c.push(d); // push dist as last elem of c
avgd += d;
}
avgd /= coords.length - 1;
// for points with long dist from prev use M instead of L
for (i = 1; i < coords.length; i++) {
c = coords[i];
newpath.push((c[2] > 5 * avgd ? "M" : "L") + points[i]);
}
return newpath.join("");
}
var gradientNameFun = function(d) { return "grd"+d.origin.Code+d.dest.Code; };
var gradientRefNameFun = function(d) { return "url(#"+gradientNameFun(d)+")"; };
var defs = svg.append("svg:defs");
// see https://apike.ca/prog_svg_patterns.html
defs.append("marker")
.attr("id", "arrowHead")
.attr("viewBox", "0 0 10 10")
.attr("refX", 10)
.attr("refY", 5)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.attr("markerWidth", 4*2)
.attr("markerHeight", 3*2)
.append("polyline")
.attr("points", "0,0 10,5 0,10 1,5")
.attr("fill", maxColor)
;
var gradient = defs.selectAll("linearGradient")
.data(links)
.enter()
.append("svg:linearGradient")
.attr("id", gradientNameFun)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", function(d) {
return d.originp[0]; })
.attr("y1", function(d) { return d.originp[1]; })
.attr("x2", function(d) { return d.destp[0]; })
.attr("y2", function(d) { return d.destp[1]; })
;
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", minColor)
.attr("stop-opacity", .0);
gradient.append("svg:stop")
.attr("offset", "80%")
.attr("stop-color", strokeFun)
.attr("stop-opacity", 1.0);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", strokeFun)
.attr("stop-opacity", 1.0);
var arcNodes = arcs.selectAll("path")
.data(links)
.enter().append("path")
.attr("stroke", gradientRefNameFun)
.attr("stroke-linecap", "round")
.attr("stroke-width", function(d) { return arcWidth(d.magnitude); })
.attr("d", function(d) {
if (useGreatCircles)
return splitPath(path(arc(d)));
else
return path({
type: "LineString",
coordinates: [d.source, d.target]
});
})
.sort(function(a, b) {
var a = a.magnitude, b = b.magnitude;
if (isNaN(a)) if (isNaN(b)) return 0; else return -1; if (isNaN(b)) return 1;
return d3.ascending(a, b);
});
arcNodes.on("mouseover", function(d) {
d3.select(this)
.attr("stroke", "red")
.attr("marker-end", "url(#arrowHead)");
})
arcNodes.on("mouseout", function(d) {
d3.select(this)
.attr("marker-end", "none")
.attr("stroke", gradientRefNameFun); })
;
arcNodes.append("svg:title")
.text(function(d) {
return d.origin.Name+" -> "+d.dest.Name+"\n"+
"Refugees in " +year+": " +magnitudeFormat(d.magnitude);
})
;
flow_links = links;
country_array = data.countries;
country_array.features[166].properties.name = "United States"
node_array = data.nodes;
for (var i = 0; i < country_array.features.length; i ++){
country_index[country_array.features[i].properties.name] = i;
}
});
function compare(shapebound, inclusive){
var paths = d3.selectAll("#arcs").selectAll("path")[0];
var circles = d3.selectAll("#centroids").selectAll("circle")[0];
var countries = d3.selectAll("#countries").selectAll("path")[0];
var hits = [];
if (queries[num] == undefined) queries[num] = [];
for (var i = 0; i < countries.length; i++){
var captured = shapebound.intersectPath([countries[i]], inclusive)
if (captured.length == 1){
hits.push(captured[0])
queries[num].push(i)
}
}
choices[num] = 0;
return hits;
}
function fill(hits){
var countries = d3.selectAll("#countries").selectAll("path")[0];
var centroids = d3.selectAll("#centroids").selectAll("circle")[0];
var arc_path = d3.selectAll("#arcs").selectAll("path")[0];
if (hits[0].tagName == "path"){
var arc_fill = [];
var country_fill = [];
d3.selectAll(".target").remove();
d3.selectAll(".arctarget").remove();
for (var i = 0; i < hits.length; i++) {
VisDock.utils.addPathLayer(hits[i], "stroke: black; fill:"+VisDock.color[num - 1] + "; opacity: 0.5");
var n = queries[num-1][i];
// Incoming
for (var j = 0; j < arc_path.length; j++){
if (arc_path[j].__data__.dest.Name == country_array.features[n].properties.name) {
var index = country_index[arc_path[j].__data__.origin.Name]
if (index != undefined) {
VisDock.utils.addPathLayer(countries[index], "stroke: white"+
";fill: " + VisDock.color[num - 1] +"; opacity: 0.5");
d3.selectAll(".VisDockPathLayer")[0][d3.selectAll(".VisDockPathLayer")[0].length-1]
.setAttributeNS(null, "class", "target")
if (country_fill.indexOf(index) == -1) country_fill.push(index)
}
VisDock.utils.addPathLayer(arc_path[j], "stroke: black;fill: none; opacity: 0.3");
d3.selectAll(".VisDockPathLayer")[0][d3.selectAll(".VisDockPathLayer")[0].length-1]
.setAttributeNS(null, "class", "arctarget")
}
}
}
for (var i = 0; i < country_fill.length; i++){
VisDock.utils.addPathLayer(countries[country_fill[i]], "stroke: white"+
";fill: none; opacity: 1");
}
}
}
var test = [];
for (var i = 0;i<flow_links.length;i++){
if (flow_links[i].dest.name == "France") {
test.push(flow_links[i])
}
}
VisDock.eventHandler = {
getHitsPolygon : function(points, inclusive) {
var shapebound = new createPolygon(points);
return compare(shapebound, inclusive);
},
getHitsLine : function(points, inclusive) {
var shapebound = new createLine(points);
return shapebound.intersectPath(d3.selectAll("Path")[0], inclusive)
},
getHitsEllipse : function(points, inclusive) {
var shapebound = new createEllipse(points);
return shapebound.intersectPath(d3.selectAll("Path")[0], inclusive)
},
setColor : function(hits) {
fill(hits)
},
changeColor : function(color, query, index) {
for (var i = 0; i < query.length; i++) {
var vis = query[i].attr("style").split("opacity:")[1].split(";")[0];
query[i][0][0].setAttributeNS(null, "style", "fill: " + color + "; opacity: " + vis)
}
},
changeVisibility : function(vis, query) {
for (var i = 0; i < query.length; i++) {
var color = query[i].attr("style").spit("fill:")[1].split(";")[0];
query[i][0][0].setAttributeNS(null, "style", "fill: " + color + "; opacity: " + vis)
}
},
removeColor : function(hits, index) {
for (var i = 0; i < hits.length; i++) {
hits[i].remove();
}
},
QueryClick : function(query, index) {
}
}
//BirdView.init(viewport, 1200, 800)
d3.select(self.frameElement).style("width", "1200px")
d3.select(self.frameElement).style("height", "800px")
</script>
</body>
</html>
Modified http://mbostock.github.com/d3/d3.v2.js to a secure url
Modified http://rawgithub.com/VisDockHub/NewVisDock/master/master/visdock.js to a secure url
Modified http://rawgithub.com/VisDockHub/NewVisDock/master/master/2D.js to a secure url
Modified http://rawgithub.com/VisDockHub/NewVisDock/master/master/IntersectionUtilities.js to a secure url
Modified http://rawgithub.com/VisDockHub/NewVisDock/master/master/visdock.utils.js to a secure url
https://mbostock.github.com/d3/d3.v2.js
https://rawgithub.com/VisDockHub/NewVisDock/master/master/visdock.js
https://rawgithub.com/VisDockHub/NewVisDock/master/master/2D.js
https://rawgithub.com/VisDockHub/NewVisDock/master/master/IntersectionUtilities.js
https://rawgithub.com/VisDockHub/NewVisDock/master/master/visdock.utils.js