|
//////////////////////////////////////////////////////////// |
|
//////////////////////// 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.85, |
|
opacityDefault = 0.75; //default opacity of chords |
|
|
|
//////////////////////////////////////////////////////////// |
|
////////////////////////// Data //////////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
var Names = ["REC1001","REC1002","REC1003","","14 Hired","16 Avaiable",""]; |
|
var Colors = ["#BFBFBF","#BFBFBF","#BFBFBF","#FFFFFF","#9EBD06","#939393","#FFFFFF"]; |
|
|
|
var respondents = 30, //Total number of respondents (i.e. the number that makes up the group) |
|
emptyPerc = 0.9, //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, 2, 10, 0], // REC1001 |
|
[ 0, 0, 0, 0, 4, 4, 0], // REC1002 |
|
[ 0, 0, 0, 0, 8, 2, 0], // REC1003 |
|
[ 0, 0, 0, 0, 0, 0, emptyStroke], // Empty stroke |
|
[ 2, 4, 8, 0, 0, 0, 0], // Hired |
|
[10, 4, 2, 0, 0, 0, 0], // Available |
|
[ 0, 0, 0, emptyStroke, 0, 0, 0] // Empty 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 colors = d3.scale.ordinal() |
|
.domain(d3.range(Names.length)) |
|
.range(Colors); |
|
|
|
var chord = d3.layout.chord() |
|
.padding(.05) |
|
.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); |
|
|
|
//////////////////////////////////////////////////////////// |
|
/////////////// Create the gradient fills ////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
//Function to create the unique id for each chord gradient |
|
function getGradID(d){ return "linkGrad-" + d.source.index + "-" + d.target.index; } |
|
|
|
//Create the gradients definitions for each chord |
|
var grads = svg.append("defs").selectAll("linearGradient") |
|
.data(chord.chords()) |
|
.enter().append("linearGradient") |
|
//Create the unique ID for this specific source-target pairing |
|
.attr("id", getGradID) |
|
.attr("gradientUnits", "userSpaceOnUse") |
|
//Find the location where the source chord starts |
|
.attr("x1", function(d,i) { return innerRadius * Math.cos((d.source.endAngle-d.source.startAngle)/2 + d.source.startAngle - Math.PI/2); }) |
|
.attr("y1", function(d,i) { return innerRadius * Math.sin((d.source.endAngle-d.source.startAngle)/2 + d.source.startAngle - Math.PI/2); }) |
|
//Find the location where the target chord starts |
|
.attr("x2", function(d,i) { return innerRadius * Math.cos((d.target.endAngle-d.target.startAngle)/2 + d.target.startAngle - Math.PI/2); }) |
|
.attr("y2", function(d,i) { return innerRadius * Math.sin((d.target.endAngle-d.target.startAngle)/2 + d.target.startAngle - Math.PI/2); }) |
|
|
|
//Set the starting color (at 0%) |
|
grads.append("stop") |
|
.attr("offset", "0%") |
|
.attr("stop-color", function(d){ return colors(d.source.index); }); |
|
|
|
//Set the ending color (at 100%) |
|
grads.append("stop") |
|
.attr("offset", "100%") |
|
.attr("stop-color", function(d){ return colors(d.target.index); }); |
|
|
|
//////////////////////////////////////////////////////////// |
|
//////////////////// Draw outer Arcs /////////////////////// |
|
//////////////////////////////////////////////////////////// |
|
|
|
var g = wrapper.selectAll("g.group") |
|
.data(chord.groups) |
|
.enter().append("g") |
|
.attr("class", "group") |
|
.on("mouseover", fade(.1)) |
|
.on("mouseout", fade(opacityDefault)); |
|
|
|
g.append("path") |
|
.style("fill", function(d) { return colors(d.index); }) |
|
.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") |
|
//change the fill to reference the unique gradient ID of the source-target combination |
|
.style("fill", function(d){ return "url(#" + getGradID(d) + ")"; }) |
|
.style("opacity", function(d) { return (Names[d.source.index] === "" ? 0 : opacityDefault); }) //Make the dummy strokes have a zero opacity (invisible) |
|
.attr("d", path) |
|
.on("mouseover", mouseoverChord) |
|
.on("mouseout", mouseoutChord); |
|
|
|
//////////////////////////////////////////////////////////// |
|
///////////////////////// 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; } |
|
|
|
//Returns an event handler for fading a given chord group. |
|
function fade(opacity) { |
|
return function(d,i) { |
|
svg.selectAll("path.chord") |
|
.filter(function(d) { return d.source.index !== i && d.target.index !== i; }) |
|
.transition() |
|
.style("opacity", opacity); |
|
}; |
|
}//fade |
|
|
|
//Highlight hovered over chord |
|
function mouseoverChord(d,i) { |
|
|
|
//Decrease opacity to all |
|
svg.selectAll("path.chord") |
|
.transition() |
|
.style("opacity", 0.1); |
|
//Show hovered over chord with full opacity |
|
d3.select(this) |
|
.transition() |
|
.style("opacity", 1); |
|
}//mouseoverChord |
|
|
|
//Bring all chords back to default opacity |
|
function mouseoutChord(d) { |
|
//Set opacity back to default for all |
|
svg.selectAll("path.chord") |
|
.transition() |
|
.style("opacity", opacityDefault); |
|
}//function mouseoutChord |