Skip to content

Instantly share code, notes, and snippets.

@jdutta
Last active November 3, 2015 21:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdutta/b34f87fd54237563618a to your computer and use it in GitHub Desktop.
Save jdutta/b34f87fd54237563618a to your computer and use it in GitHub Desktop.
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] + ' &rarr; ' + 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment