Last active
November 24, 2015 17:22
-
-
Save rarpal/3dd7f3950d3d8c570eb6 to your computer and use it in GitHub Desktop.
Interactive chord diagram for health care analytics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font: 10px sans-serif; | |
} | |
.tooltip-rect { | |
fill: grey; | |
opacity: .8; | |
} | |
.tooltip-text { | |
font-size: 12; | |
fill: white; | |
} | |
.chord path { | |
fill-opacity: .67; | |
stroke: #000; | |
stroke-width: .5px; | |
} | |
</style> | |
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
</head> | |
<body> | |
<script> | |
// Setup data | |
// Matrix created from the dataset | |
var matrix = [ | |
// <---------------------Past------------------------> | |
// Non-users Healthy Low Moderate High Very high Present | |
[488, 14, 97, 225, 108, 44], // Non-users | |
[240, 2984, 514, 1373, 591, 266], // Healthy | |
[4, 53, 1861, 833, 703, 268], // Low | |
[16, 143, 196, 2999, 1633, 1011], // Moderate | |
[3, 9, 8, 77, 1042, 945], // High | |
[0, 2, 1, 10, 148, 161] // Very high | |
]; | |
// The lables for the chord groups | |
var groupLables = ["Non-users", "Healthy", "Low", "Moderate", "High", "Very high"]; | |
var groupColors = ["blue", "green", "yellow", "orange", "purple", "red"]; | |
// The chord lyaout maps the matirx data into a group of arcs with their radian angles | |
var chord = d3.layout.chord() | |
.padding(.05) | |
.sortSubgroups(d3.descending) | |
.matrix(matrix); | |
// The size of the svg viewport and the radius values for the circle used for the chart | |
var width = 960, | |
height = 600, | |
innerRadius = Math.min(width, height) * .31, | |
outerRadius = innerRadius * 1.1; | |
// Create the svg viewport | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
// Append a group and move it to the center of the viewport | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
// Create group for user instructions | |
var ig = d3.select("svg").append("g") | |
.attr("transform", function() { return "translate(20,500)"; }) | |
ig.append("rect") | |
.attr("width", 200).attr("height", 50) | |
.attr("rx", 10).attr("ry", 10) | |
.attr("class", "tooltip-rect"); | |
ig.append("text").attr("x", 10).attr("y", 5) | |
.attr("class", "tooltip-text") | |
.append("tspan").attr("x", 10).attr("dy", 15) | |
.text("-> Mouseover the arcs for group totals") | |
.append("tspan").attr("x", 10).attr("dy", 15) | |
.text("-> Mouseover the chords for values"); | |
// Setup the arc path generator with the predetermined radius values | |
var arc = d3.svg.arc() | |
.innerRadius(innerRadius) | |
.outerRadius(outerRadius); | |
// Setup a group for the chords. Each group contains the data mappings for each chord which will be used | |
// next when generatin the paths | |
var chordGroup = svg.selectAll() | |
.data(chord.groups) | |
.enter() | |
.append("g"); | |
// Generate paths for each chord using the chord group above | |
chordGroup.append("path") | |
.style("fill", function(d) { return groupColors[d.index]; }) | |
.style("stroke", "black") | |
.attr("id", function(d, i) {return "group" + i ;}) | |
.attr("d", arc) | |
.on("mouseover.one", groupFade(.1)) | |
.on("mouseover.two", groupSelect()) | |
.on("mouseout.one", groupFade(1)) | |
.on("mouseout.two", groupDeselect()); | |
// Pass each chord group to the groupTicks method which will the require ticks and place them in groups | |
// Each tick in the group is transformed by rotating them 180 degreese perpendicular to the corresponding radiun angle | |
// and then moving it to the outer radius of the circle | |
var ticks = chordGroup.selectAll() | |
.data(groupTicks) | |
.enter() | |
.append("g") | |
.attr("transform", function(d) { | |
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + " translate(" + outerRadius + ",0)"; | |
}); | |
// For each of the tick groups append the tick line and set its stroke | |
ticks.append("line") | |
.attr("x1", 1) | |
.attr("y1", 0) | |
.attr("x2", 5) | |
.attr("y2", 0) | |
.style("stroke", "#000"); | |
// For each tick group append the tick lable | |
// The tick lable is only set for numbers divisible by 5, others will be blank | |
ticks.append("text") | |
.attr("x", 8) | |
.attr("dy", ".35em") | |
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; }) | |
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) | |
.style("font-size", 8) | |
.text(function(d) { return d.label; }); | |
// The following is an alternative way of displaying lables along the arcs of the circle | |
/* | |
// Add a text path label. | |
var groupText = chordGroup.append("text") | |
.attr("x", 8) | |
.attr("dy", 15) | |
//.attr("x", 8) | |
//.attr("dy", ".35em") | |
//.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(16)" : null; }) | |
//.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) | |
//.style("fill", "Blue") | |
.text(function(d) { return d.label; }); | |
groupText.append("textPath") | |
.attr("xlink:href", function(d, i) { return "#group" + i ;}) | |
.text(function(d, i) { return datatext[i] ;}); | |
*/ | |
// For each chord in the group group append the group lable. | |
// This is similar to how the ticks were added, but here we use the chord group created ealier | |
// For each chord in chord group we find the middle between the start and end angles, then place the text there by | |
// first rotating it 180 degrees perpendicular to the angle and then moving it to 50 pixels outside the inner radius | |
chordGroup.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 + 50) + ")" + (d.angle > Math.PI ? "rotate(180)" : ""); | |
} ) | |
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) | |
.style("font-size", 10).style("font-weight", "bold") | |
.text(function(d,i) { return groupLables[i]; }); | |
// Append a new group to the viewport, then append the chord paths using the chord path generator | |
// The chords are bound to the data provided by the chord layout whcih was derived from the matrix | |
svg.append("g") | |
.attr("class", "chord") | |
.selectAll("path") | |
.data(chord.chords) | |
.enter() | |
.append("path") | |
.attr("d", d3.svg.chord().radius(innerRadius)) | |
.style("fill", function(d) { return groupColors[d.target.index]; }) | |
.style("opacity", 1) | |
.on("mouseover.one", chordFade(.1)) | |
.on("mouseover.two", chordSelect()) | |
.on("mouseout.one", chordFade(1)) | |
.on("mouseout.two", chordDeselect()); | |
// Returns an array of tick angles and labels, given a group. | |
function groupTicks(d) { | |
var k = (d.endAngle - d.startAngle) / d.value; | |
return d3.range(0, d.value, 100).map(function(v, i) { | |
return { | |
angle: v * k + d.startAngle, | |
label: i % 5 ? null : v | |
}; | |
}); | |
} | |
// Returns an event handler for fading a given chord group. | |
function groupFade(opacity) { | |
return function(g, i) { | |
svg.selectAll(".chord path") | |
.filter(function(d) { | |
return d.source.index != i && d.target.index != i; | |
}) | |
.style("opacity", opacity); | |
}; | |
} | |
function groupSelect() { | |
return function(g, i) { | |
var tg = d3.select("svg").append("g") | |
.attr("id", "tt") | |
.attr("transform", function() { return "translate(750,40)"; }) | |
tg.append("rect") | |
.attr("width", 200).attr("height", 80) | |
.attr("rx", 10).attr("ry", 10) | |
.attr("class", "tooltip-rect"); | |
tg.append("text").attr("x", 10).attr("y", 5) | |
.attr("class", "tooltip-text") | |
.append("tspan").attr("x", 10).attr("dy", 15) | |
.text(groupLables[g.index] + " group total : " + Math.round(g.value)); | |
} | |
} | |
function groupDeselect() { | |
return function(c, i) { | |
d3.select("#tt").remove(); | |
}; | |
} | |
function chordFade(opacity) { | |
return function(c, i) { | |
svg.selectAll(".chord path") | |
.filter(function(d) { | |
return !(d.source.index == c.source.index && d.target.index == c.target.index); | |
}) | |
.style("opacity", opacity); | |
}; | |
} | |
function chordSelect() { | |
return function(c, i) { | |
var tg = d3.select("svg").append("g") | |
.attr("id", "tt") | |
.attr("transform", function() { return "translate(750,40)"; }) | |
tg.append("rect") | |
.attr("width", 200).attr("height", 80) | |
.attr("rx", 10).attr("ry", 10) | |
.attr("class", "tooltip-rect"); | |
tg.append("text").attr("x", 10).attr("y", 5) | |
.attr("class", "tooltip-text") | |
.append("tspan").attr("x", 10).attr("dy", 15) | |
.text("from " + groupLables[c.source.subindex] + " to " + groupLables[c.source.index] + " : " + c.source.value) | |
.append("tspan").attr("x", 10).attr("dy", 15) | |
.text("from " + groupLables[c.target.subindex] + " to " + groupLables[c.target.index] +" : " + c.target.value); | |
} | |
} | |
function chordDeselect() { | |
return function(c, i) { | |
d3.select("#tt").remove(); | |
}; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment