|
//////////////////////////////////////////////////////////// |
|
//////////////////////// Set-up //////////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
var screenWidth = $(window).width(); |
|
|
|
var margin = {left: 50, top: 10, right: 50, bottom: 10}, |
|
width = Math.min(screenWidth, 800) - margin.left - margin.right, |
|
height = Math.min(screenWidth, 800)*5/6 - margin.top - margin.bottom; |
|
|
|
var svg = d3.select("#chart").append("svg") |
|
.attr("width", (width + margin.left + margin.right)) |
|
.attr("height", (height + margin.top + margin.bottom)); |
|
|
|
var wrapper = svg.append("g").attr("class", "chordWrapper") |
|
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");; |
|
|
|
var outerRadius = Math.min(width, height) / 2 - 100, |
|
innerRadius = outerRadius * 0.95, |
|
opacityDefault = 0.7; //default opacity of chords |
|
|
|
//////////////////////////////////////////////////////////// |
|
////////////////////////// Data //////////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
var Names = ["X","Y","Z","","C","B","A",""]; |
|
|
|
var respondents = 95, //Total number of respondents (i.e. the number that makes up the group) |
|
emptyPerc = 0.4, //What % of the circle should become empty in comparison to the visible arcs |
|
emptyStroke = Math.round(respondents*emptyPerc); //How many "units" would define this empty percentage |
|
var matrix = [ |
|
[0,0,0,0,10,5,15,0], //X |
|
[0,0,0,0,5,15,20,0], //Y |
|
[0,0,0,0,15,5,5,0], //Z |
|
[0,0,0,0,0,0,0,emptyStroke], //Dummy stroke |
|
[10,5,15,0,0,0,0,0], //C |
|
[5,15,5,0,0,0,0,0], //B |
|
[15,20,5,0,0,0,0,0], //A |
|
[0,0,0,emptyStroke,0,0,0,0] //Dummy stroke |
|
]; |
|
//Calculate how far the Chord Diagram needs to be rotated clockwise |
|
//to make the dummy invisible chord center vertically |
|
var offset = Math.PI * (emptyStroke/(respondents + emptyStroke)) / 2; |
|
|
|
var chord = d3.layout.chord() |
|
.padding(.02) |
|
.sortSubgroups(d3.descending) //sort the chords inside an arc from high to low |
|
.sortChords(d3.descending) //which chord should be shown on top when chords cross. Now the biggest chord is at the bottom |
|
.matrix(matrix); |
|
|
|
var arc = d3.svg.arc() |
|
.innerRadius(innerRadius) |
|
.outerRadius(outerRadius) |
|
.startAngle(startAngle) //startAngle and endAngle now include the offset in degrees |
|
.endAngle(endAngle); |
|
|
|
var path = d3.svg.chord() |
|
.radius(innerRadius) |
|
.startAngle(startAngle) |
|
.endAngle(endAngle); |
|
|
|
var fill = d3.scale.ordinal() |
|
.domain(d3.range(Names.length)) |
|
.range(["#C4C4C4","#C4C4C4","#C4C4C4","#E0E0E0","#EDC951","#CC333F","#00A0B0","#E0E0E0"]); |
|
|
|
//////////////////////////////////////////////////////////// |
|
//////////////////// Draw outer Arcs /////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
var g = wrapper.selectAll("g.group") |
|
.data(chord.groups) |
|
.enter().append("g") |
|
.attr("class", "group");; |
|
|
|
g.append("path") |
|
.style("stroke", function(d,i) { return (Names[i] === "" ? "none" : "#00A1DE"); }) |
|
.style("fill", function(d,i) { return (Names[i] === "" ? "none" : "#00A1DE"); }) |
|
.attr("d", arc); |
|
|
|
//////////////////////////////////////////////////////////// |
|
////////////////////// Append Names //////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
//The text needs to be rotated with the offset in the clockwise direction |
|
g.append("text") |
|
.each(function(d) { d.angle = ((d.startAngle + d.endAngle) / 2) + offset;}) //Slightly altered function |
|
.attr("dy", ".35em") |
|
.attr("class", "titles") |
|
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) |
|
.attr("transform", function(d,i) { |
|
var c = arc.centroid(d); |
|
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" |
|
+ "translate(" + (innerRadius + 55) + ")" |
|
+ (d.angle > Math.PI ? "rotate(180)" : "") |
|
}) |
|
.text(function(d,i) { return Names[i]; }); |
|
|
|
//+ "translate(" + (innerRadius + 55) + ")" |
|
|
|
//////////////////////////////////////////////////////////// |
|
//////////////////// Draw inner chords ///////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
var chords = wrapper.selectAll("path.chord") |
|
.data(chord.chords) |
|
.enter().append("path") |
|
.attr("class", "chord") |
|
.style("stroke", "none") |
|
.style("fill", "#C4C4C4") |
|
.style("opacity", function(d) { return (Names[d.source.index] === "" ? 0 : opacityDefault); }) //Make the dummy strokes have a zero opacity (invisible) |
|
.attr("d", path); |
|
|
|
//////////////////////////////////////////////////////////// |
|
///////////////////////// Tooltip ////////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
//Arcs |
|
g.append("title") |
|
.text(function(d, i) {return Math.round(d.value) + " people in " + Names[i];}); |
|
|
|
//Chords |
|
chords.append("title") |
|
.text(function(d) { |
|
return [Math.round(d.source.value), " people from ", Names[d.target.index], " to ", Names[d.source.index]].join(""); |
|
}); |
|
|
|
//////////////////////////////////////////////////////////// |
|
////////////////// Extra Functions ///////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
//Include the offset in de start and end angle to rotate the Chord diagram clockwise |
|
function startAngle(d) { return d.startAngle + offset; } |
|
function endAngle(d) { return d.endAngle + offset; } |