//////////////////////////////////////////////////////////// //////////////////////// 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