|
var Chart = (function(window,d3) { |
|
|
|
var svg = d3.select('svg'), |
|
diameter, |
|
margin, |
|
outerRadius, |
|
innerRadius, |
|
options, |
|
color, |
|
chord, |
|
arc, |
|
ribbon, |
|
wrapper, |
|
group, |
|
ribbons, |
|
higlight = 'Both', |
|
toplist, |
|
matrix, |
|
nameByIndex, |
|
n = 0; |
|
|
|
d3.csv('remittancematrix2016_Oct2017_updated.csv', init); |
|
|
|
/** |
|
* init chart |
|
*/ |
|
function init(error, data) { |
|
if (error) throw error; |
|
|
|
toplist = createTopList(data); |
|
matrix = d3.transpose(createMatrix(toplist)); |
|
|
|
color = d3.scaleOrdinal(d3.schemeCategory20c); |
|
|
|
chord = d3.chord() |
|
.padAngle(0.025) |
|
.sortSubgroups(d3.descending) |
|
.sortChords(d3.descending); |
|
arc = d3.arc(); |
|
ribbon = d3.ribbon(); |
|
|
|
// Compute a unique index for each country. |
|
nameByIndex = d3.map(); |
|
n = 0; |
|
Object.keys(toplist[0]).forEach(function(d) { |
|
nameByIndex.set(n++, d); |
|
}); |
|
|
|
wrapper = svg.append('g').datum(chord(matrix)); |
|
group = wrapper.append('g').attr('class', 'groups').selectAll('g') |
|
.data(function(chords) { return chords.groups; }) |
|
.enter() |
|
.append('g') |
|
.attr('class', 'group') |
|
.on('mouseover', mouseover) |
|
.on('mouseout', mouseout); |
|
|
|
options = d3.selectAll('#options input').on('change', function() { |
|
higlight = this.value; |
|
}); |
|
|
|
render(); |
|
} |
|
|
|
/** |
|
* render |
|
*/ |
|
function render() { |
|
diameter = Math.min(window.innerWidth, 960); |
|
margin = 100; |
|
outerRadius = (diameter - margin * 2) / 2; |
|
innerRadius = outerRadius - 30; |
|
|
|
// cleaup for re-render |
|
if(ribbons) { |
|
group.selectAll('*').remove(); |
|
ribbons.remove(); |
|
} |
|
|
|
arc.innerRadius(innerRadius).outerRadius(outerRadius); |
|
ribbon.radius(innerRadius); |
|
|
|
svg.attr('width', diameter).attr('height',diameter); |
|
wrapper.attr('transform', 'translate(' + (outerRadius + margin) + ',' + (outerRadius + margin) + ')'); |
|
|
|
group.append('path') |
|
.style('fill', function(d) { return color(d.index); }) |
|
// .style('stroke', function(d) { return d3.rgb(color(d.index)).darker(); }) |
|
.attr('d', arc); |
|
|
|
group.append('text') |
|
.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(' + (innerRadius + 36) + ')' + (d.angle > Math.PI ? 'rotate(180)' : ''); |
|
}) |
|
.style('text-anchor', function(d) { return d.angle > Math.PI ? 'end' : null; }) |
|
.text(function(d) { return nameByIndex.get(d.index); }) |
|
.on('mouseover', mouseover) |
|
.on('mouseout', mouseout); |
|
|
|
ribbons = wrapper.append('g') |
|
.attr('class', 'ribbons') |
|
.selectAll('path') |
|
.data(function(chords) { return chords; }) |
|
.enter().append('path') |
|
.attr('d', ribbon) |
|
.attr('class', 'ribbon') |
|
.style('fill', function(d) { return color(d.target.index); }) |
|
.style('stroke', function(d) { return d3.rgb(color(d.target.index)).darker(); }); |
|
} |
|
|
|
// auxiliary functions. Seems clumsy... |
|
function mouseover (d, i) { |
|
|
|
group.classed('fade', function(p){ |
|
return d.index !== i; |
|
}); |
|
|
|
ribbons.classed('fade', function(p) { |
|
if (higlight === 'Inflows') { |
|
return p.source.index !== i; |
|
} else if (higlight === 'Outflows') { |
|
return p.target.index !== i; |
|
} else { |
|
return p.source.index !== i && p.target.index !== i; |
|
} |
|
}); |
|
|
|
ribbons.classed('focus', function(p) { |
|
if (higlight === 'Inflows') { |
|
return p.source.index === i; |
|
} else if (higlight === 'Outflows') { |
|
return p.target.index === i; |
|
} else { |
|
return p.source.index === i || p.target.index === i; |
|
} |
|
}); |
|
} |
|
|
|
function mouseout() { |
|
ribbons.classed('fade', false); |
|
ribbons.classed('focus', false); |
|
group.classed('fade', false); |
|
group.classed('focus', false); |
|
} |
|
|
|
// filter the complete list |
|
function createTopList(data) { |
|
var selection = []; |
|
// list of the 20 top inflow and outflow countries |
|
var filter = ['United States', 'Saudi Arabia', 'Switzerland', 'Germany', 'China', 'Russian Federation', 'Kuwait', 'France', 'Qatar', 'Luxembourg', 'Korea, Rep.', 'United Kingdom', 'Italy', 'Netherlands', 'Australia', 'India', 'Israel', 'Indonesia', 'Japan', 'Canada', 'Norway', 'United States', 'Saudi Arabia', 'Switzerland', 'Germany', 'China', 'Russian Federation', 'Kuwait', 'France', 'Qatar', 'Luxembourg', 'Korea, Rep.', 'United Kingdom', 'Italy', 'Netherlands', 'Australia', 'India', 'Israel', 'Indonesia', 'Japan', 'Canada', 'Norway', 'India', 'China', 'Philippines', 'Mexico', 'France', 'Nigeria', 'Pakistan', 'Germany', 'Egypt, Arab Rep.', 'Bangladesh', 'Vietnam', 'Spain', 'Belgium', 'Italy', 'Indonesia', 'Lebanon', 'Guatemala', 'Sri Lanka', 'Morocco', 'Poland', 'Russian Federation']; |
|
data.forEach(function(d,i) { |
|
d3.set(filter).values().forEach(function(s,i) { |
|
if(d.From === s) { |
|
for (i in d) { |
|
if (filter.indexOf(i) < 0) { delete d[i]; } |
|
} |
|
selection.push(d); |
|
} |
|
}); |
|
}); |
|
return selection; |
|
} |
|
|
|
// Construct a square matrix |
|
function createMatrix(data) { |
|
var array = []; |
|
data.forEach(function(d,i) { |
|
array[i] = []; // create outer array |
|
delete d.From; // remove 'From colum' |
|
var values = Object.values(d); // extract list of values |
|
values.forEach(function(d,j) { // loop values |
|
d = +d; // coerce type |
|
if (typeof d == 'undefined' || isNaN(d)) { // check for errors in data |
|
console.warn(i, j, d); // debug any errors |
|
} else { |
|
array[i][j] = d; // create inner array wih coerced type |
|
} |
|
}); |
|
}); |
|
return array; |
|
} |
|
|
|
return { |
|
render : render |
|
}; |
|
|
|
})(window,d3); |
|
|
|
window.addEventListener('resize', Chart.render); |