Skip to content

Instantly share code, notes, and snippets.

@rarpal
Last active November 24, 2015 17:22
Show Gist options
  • Save rarpal/3dd7f3950d3d8c570eb6 to your computer and use it in GitHub Desktop.
Save rarpal/3dd7f3950d3d8c570eb6 to your computer and use it in GitHub Desktop.
Interactive chord diagram for health care analytics
<!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