Demo loading The Migrant Files data hosted via https://github.com/enjalot/migrants
Built with blockbuilder.org
forked from enjalot's block: The Migrant Files: Money
Demo loading The Migrant Files data hosted via https://github.com/enjalot/migrants
Built with blockbuilder.org
forked from enjalot's block: The Migrant Files: Money
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
#info { | |
position: absolute; | |
top: 0; | |
right: 0; | |
width: 300px; | |
height: auto; | |
background-color: #eee; | |
border: 1px solid #ccc; | |
} | |
svg { width: 100%; height: 100%; font-size: 10px; overflow-y: scroll; } | |
</style> | |
</head> | |
<body> | |
<div id="info"></div> | |
<svg></svg> | |
<script> | |
var config = { | |
width: 1000, | |
height: 1000, | |
textFontSize: 11, | |
fadedOpacity: 0.03, | |
innerRadius: 245, | |
arcThickness: 20 | |
}; | |
function toNumber(s) { | |
// convert strings with spaces instead of commas into numbers | |
var n = s.replace(/[w]/g, "") //remove any whitespace | |
.replace(/ /g, "") //utf8 space? | |
return +n; //convert to number | |
} | |
function process(d) { | |
var processed = { | |
start: d.Start, | |
end: d.End, | |
year: +d.Year, | |
// we just deal with the price in Euros adjusted for inflation | |
price: toNumber(d["Price EUR 2014"]), | |
service: d.Service, | |
route: d.route, | |
author: d.author, | |
comment: d.Comment, | |
source: d.Source, | |
} | |
return processed; | |
} | |
// return arr of all locations [l1, l2, ...] and also | |
// a matrix such that we know price for l1 -> l2 | |
function getLocationsAndMatrix(data) { | |
var locMap = {}; | |
data.forEach(function (o) { | |
// artificial logic to reduce num locs: ignore data point if price < threshold | |
if (o.price > 10000) { | |
locMap[o.start] = true; | |
locMap[o.end] = true; | |
} | |
}); | |
var locs = Object.keys(locMap) | |
var matrix = []; | |
// artificially reduce size of locs array | |
//locs.length = 25; | |
var nLocs = locs.length; | |
locs.forEach(function (l) { | |
var arr = []; | |
for (var i=0; i<nLocs; i++) { | |
arr.push(0); | |
} | |
matrix.push(arr); | |
}); | |
// now go through the original data | |
// for each data, find the index of start and end loc in locs array | |
// then add the cost in the matrix | |
data.forEach(function (o) { | |
var startIndex = locs.indexOf(o.start); | |
var endIndex = locs.indexOf(o.end); | |
if (startIndex > -1 && endIndex > -1) { | |
matrix[startIndex][endIndex] += o.price; | |
} | |
}); | |
return { | |
locations: locs, | |
matrix: matrix | |
}; | |
} | |
d3.csv("http://enjalot.github.io/migrants/money.csv", function(err, rawdata) { | |
//console.log("rawdata", rawdata); | |
var data = rawdata.map(process); | |
console.log("data", data); | |
var processedData = getLocationsAndMatrix(data); | |
console.log("processed data", processedData); | |
visualize(processedData.locations, processedData.matrix); | |
}); | |
function visualize(hosts, matrix) { | |
var svg = d3.select('svg'); | |
var gRoot = svg.append('svg:g') | |
.attr('transform', 'translate(375, 355)'); | |
var fillColorFn = d3.scale.category20c(); | |
var chord = d3.layout.chord() | |
.padding(0.05) | |
.sortSubgroups(d3.descending) | |
.sortChords(d3.descending); | |
var arc = d3.svg.arc() | |
.innerRadius(config.innerRadius) | |
.outerRadius(config.innerRadius + config.arcThickness); | |
chord.matrix(matrix); | |
var gGroup = gRoot.selectAll('.group') | |
.data(chord.groups) | |
.enter().append('svg:g') | |
.attr('class', 'group'); | |
gGroup.append('path') | |
.classed('arc', true) | |
.style('fill', function(d) { return fillColorFn(d.index); }) | |
.style('stroke', function(d) { return fillColorFn(d.index); }) | |
.attr('d', arc) | |
.on('mouseenter', function (d) { | |
var activeArc = {}; | |
var tips = []; | |
gRoot.selectAll('path.chord').each(function (d2) { | |
if (!(d2.source.index == d.index || d2.target.index == d.index)) { | |
d3.select(this) | |
.transition() | |
.style('opacity', config.fadedOpacity) | |
} else { | |
var si = d2.source.index; | |
var ti = d2.target.index; | |
activeArc[si] = true; | |
activeArc[ti] = true; | |
d3.select(this).moveToFront(); | |
tips.push(hosts[si] + ' → ' + hosts[ti] + ' (' + matrix[si][ti] + ')'); | |
} | |
}); | |
d3.select('#info').html(tips.join('<br/>')); | |
gRoot.selectAll('path.arc').each(function (d2) { | |
if (!activeArc[d2.index]) { | |
d3.select(this) | |
.transition() | |
.style('opacity', config.fadedOpacity) | |
d3.select('text.label-' + d2.index) | |
.transition() | |
.style('opacity', config.fadedOpacity) | |
} | |
}); | |
}) | |
.on('mouseleave', function (d) { | |
d3.select('#info').html(''); | |
gRoot.selectAll('path.arc') | |
.transition() | |
.style('opacity', 1); | |
gRoot.selectAll('path.chord') | |
.transition() | |
.style('opacity', 1); | |
gRoot.selectAll('text.label') | |
.transition() | |
.style('opacity', 1); | |
}); | |
gGroup.append('text') | |
.attr('class', function (d) { return 'label label-' + d.index; }) | |
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; }) | |
.attr('dy', '.35em') | |
.attr('transform', function(d) { | |
return 'rotate(' + (d.angle * 180 / Math.PI - 90) + ')' | |
+ 'translate(' + (config.innerRadius + config.arcThickness + 5) + ')' | |
+ (d.angle > Math.PI ? 'rotate(180)' : ''); | |
}) | |
.style('text-anchor', function(d) { return d.angle > Math.PI ? 'end' : null; }) | |
.style('font-size', config.textFontSize) | |
.text(function(d) { | |
return hosts[d.index]; | |
}); | |
gRoot.selectAll('.chord') | |
.data(chord.chords) | |
.enter().append('path') | |
.attr('class', 'chord') | |
.style('stroke', function(d) { return d3.rgb(fillColorFn(d.source.index)).darker(); }) | |
.style('stroke-opacity', '0.4') | |
.style('fill', function(d) { return fillColorFn(d.source.index); }) | |
.attr('d', d3.svg.chord().radius(config.innerRadius)); | |
} | |
d3.selection.prototype.moveToFront = function() { | |
return this.each(function() { | |
this.parentNode.appendChild(this); | |
}); | |
}; | |
</script> | |
</body> |