Skip to content

Instantly share code, notes, and snippets.

@jrzief
Last active September 6, 2019 18:19
Show Gist options
  • Save jrzief/80a99ed1686f498d071f158c3c08631f to your computer and use it in GitHub Desktop.
Save jrzief/80a99ed1686f498d071f158c3c08631f to your computer and use it in GitHub Desktop.
Open Chord Diagram -Commuter Flow
license: mit

This example uses the D3 chord layout function to show the commute patterns of people who belong to an affinity group. It gives some insight as to the availability of group members for afterwork events.

The chord layout implements the concept of circular visualization introduced in Circos. This particular example is "open" in that the matrix used to compute the chords has a set of null rows. Affinity group members were commuting to locations both inside and outside of their home region.

Notable features include: responsive image resizing, chord layout rotation, and a generalized chord visualization function with configuration object.

forked from StewartNoyce's block: Open Chord Diagram

<!DOCTYPE html>
<meta charset="utf-8">
<!-- affinity group data, circle rotation, auto-resize svg image, chord function, configuration -->
<title>Open Chord Example</title>
<style>
#visual {
font: 14px sans-serif;
}
.chord path {
fill-opacity: .67;
stroke: #000;
stroke-width: .5px;
}
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) {
#visual {
-webkit-user-select: none;
font-size: 1.2em;
}
}
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
#visual {
-webkit-user-select: none;
}
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var visual = document.getElementById("visual");
// persons moving between Marin, Sonoma, Napa, SF, EB, SV and other regions
var matrix = [
[7235,119,131,128,606,167,497,175], //Princeton
[1045,4029,1021,498,1227,67,691,2578], //Ewing
[1790,3050,12757,589,2846,490,1909,7465], //Hamilton
[1202,675,345,1667,739,41,419,653], //Hopewell Twp
[1432,789,11171,546,4353,214,1061,1657], //Lawrence
[563,213,538,115,487,1907,582,579], //East Windsor
[708,180,418,227,547,349,2681,244], //West Windsor
[1560,2401,3751,68,1660,398,1044,10,975] //Trenton
];
var array = [ "Princeton", "Ewing", "Hamilton", "Hopewell Twp", "Lawrence", "East Windsor", "West Windsor", "Trenton" ];
var rotation = -0.7;
var chord_options = {
"gnames": array,
"rotation": rotation,
"colors": ["#034e7b","#feb24c","#b10026","#238443","#fdbb84","#ffffb2","#fed976","#008FC8"]
};
function Chord(container, options, matrix) {
// initialize the chord configuration variables
var config = {
width: 640,
height: 560,
rotation: 0,
textgap: 26,
colors: ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"]
};
// add options to the chord configuration object
if (options) {
extend(config, options);
}
// set chord visualization variables from the configuration object
var offset = Math.PI * config.rotation,
width = config.width,
height = config.height,
textgap = config.textgap
colors = config.colors;
// set viewBox and aspect ratio to enable a resize of the visual dimensions
var viewBoxDimensions = "0 0 " + width + " " + height,
aspect = width / height;
if (config.gnames) {
gnames = config.gnames;
} else {
// make a list of names
gnames = [];
for (var i=97; i<matrix.length; i++) {
gnames.push(String.fromCharCode(i));
}
}
// start the d3 magic
var chord = d3.layout.chord()
.padding(.05)
.sortSubgroups(d3.descending)
.matrix(matrix);
var innerRadius = Math.min(width, height) * .31,
outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
.domain(d3.range(matrix.length-1))
.range(colors);
var svg = d3.select("body").append("svg")
.attr("id", "visual")
.attr("viewBox", viewBoxDimensions)
.attr("preserveAspectRatio", "xMinYMid") // add viewBox and preserveAspectRatio
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
g.append("svg:path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("id", function(d, i) { return "group" + d.index; })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
g.append("svg:text")
.each(function(d) {d.angle = ((d.startAngle + d.endAngle) / 2) + offset; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (outerRadius + textgap) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return gnames[d.index]; });
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius).startAngle(startAngle).endAngle(endAngle))
.style("fill", function(d) { return fill(d.source.index); })
.style("opacity", 1)
.append("svg:title")
.text(function(d) {
return d.source.value + " people from " + gnames[d.source.index] + " commute to " + gnames[d.target.index];
});
// helper functions start here
function startAngle(d) {
return d.startAngle + offset;
}
function endAngle(d) {
return d.endAngle + offset;
}
function extend(a, b) {
for( var i in b ) {
a[ i ] = b[ i ];
}
}
// Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(g, i) {
svg.selectAll(".chord path")
.filter(function(d) { return d.source.index != i && d.target.index != i; })
.transition()
.style("opacity", opacity);
};
}
window.onresize = function() {
var targetWidth = (window.innerWidth < width)? window.innerWidth : width;
var svg = d3.select("#visual")
.attr("width", targetWidth)
.attr("height", targetWidth / aspect);
}
}
window.onload = function() {
Chord(visual, chord_options, matrix);
}
d3.select(self.frameElement).style("height", "600px");
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment