Circular layout for visualizing relationships or network flows with multiple types or categories. This is a modification of d3-chord
to enable another dimension of data to be shown between nodes. Hovering over nodes filters chords for that node. Hovering over a chord will select all chords with the same category.
Sample data shows migration flows between US regions by nativity from 2014-2015 (in thousands). Derived from 2015 US census data Geographic Mobility (Table 12)
forked from chornbaker's block: Chord Diagram with Multiple Categories
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<title>US migration between regions by nativity: d3-multichord example</title>
<meta name="author" content="Charles Hornbaker">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/d3-format.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="d3-multichord.js"></script>
<style>
#circle circle {
fill: none;
pointer-events: all;
}
.group path {
stroke: #000;
stroke-width: .25px;
fill-opacity: 0.9;
}
path.chord {
stroke: #000;
stroke-width: .25px;
fill-opacity: 0.9;
}
path.fade {
display: none;
}
</style>
<div id="vis"></div>
<script>
// Adapted from Mike Bostock's UberData Chord diagram example
// https://bost.ocks.org/mike/uberdata/
// Overall page margins
var HEIGHT = 600,
WIDTH = 960;
outerRadius = Math.min(WIDTH, HEIGHT) / 2 - 40
innerRadius = outerRadius - 30;
// Formatting functions
var formatPercent = d3.format(".1%");
var formatNumber = function (x){
if (Math.abs(x) >= 1e9) {
return d3.format(",.2f")(x / 1e9) + " Billion"
}
else if (Math.abs(x) >= 1e6) {
return d3.format(",.2f")(x / 1e6) + " Million"
}
else {
return d3.format(",.0f")(x)
}
}
// Chord chart elements
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var layout = d3.multichord()
.padAngle(.05)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var path = d3.ribbon()
.radius(innerRadius);
var svg = d3.select("#vis").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT)
// .attr("x", CHORD_VIS.X)
// .attr("y", CHORD_VIS.Y)
d3.queue()
.defer(d3.json, "migration_regions.json")
.await(ready);
function ready(error, data) {
if (error) throw error;
var nodes = data.nodes,
categories = data.categories;
var chords = layout(data.links)
// Compute the chord layout.
var g = svg.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + (WIDTH / 2) + "," + (HEIGHT / 2) + ")")
.datum(chords);
g.append("circle")
.attr("r", outerRadius)
g.append("g").attr("id", "groups");
g.append("g").attr("id", "chords");
var group, groupPath, groupText, chord;
// Add a group per neighborhood.
group = g.select("#groups")
.selectAll("g")
.data(function(chords){ return chords.groups})
.enter().append("g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", mouseover_restore);
// Add the group arc.
groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return nodes[i].color; });
// Add a text label.
groupText = group.append("text")
.attr("x", 6)
.attr("dy", 15)
.append("textPath")
.attr("xlink:href", function(d, i) { return "#group" + i; })
.text(function(d, i) { return nodes[i].name; })
.attr("opacity", function(d, i) {
// Hide labels that don't fit
if (groupPath._groups[0][i].getTotalLength() / 2 - 25 < this.getComputedTextLength()) {
return 0;
} else {
return 1;
};
})
// Add a mouseover title.
group.append("title").text(function(d, i) {
return nodes[i].name
+ "\n" + "In: " + formatNumber(chords.groups[i].value.in)
+ "\n" + "Out: " + formatNumber(chords.groups[i].value.out);
});
// Add the chords.
chord = g.select("#chords").selectAll("g")
.data(function(chords) { return chords;})
.enter().append("g")
.attr("class", "chord");
chord.append("path")
.attr("class", "chord")
.style("fill", function(d) { return nodes[d.source.index].color; })
.attr("d", path)
.on("mouseover", mouseover_types)
.on("mouseout", mouseover_restore);
// Add a mouseover title for each chord.
chord.append("title").text(function(d) {
return categories[d.source.category].name
+ "\n" + nodes[d.source.index].name
+ " → " + nodes[d.target.index].name
+ ": " + formatNumber(d.source.value)
+ "\n" + nodes[d.target.index].name
+ " → " + nodes[d.source.index].name
+ ": " + formatNumber(d.target.value);
});
function mouseover(d) {
g.select("#chords").selectAll("path")
.classed("fade", function(p) {
return p.source.index != d.index
&& p.target.index != d.index;
});
}
function mouseover_types(d) {
g.select("#chords").selectAll("path")
.classed("fade", function(p) {
return p.source.category != d.source.category
&& p.target.category != d.target.category;
});
}
function mouseover_restore(d) {
g.select("#chords").selectAll("path")
.classed("fade", function(p) {
return false;
});
}
}
</script>
https://html5shim.googlecode.com/svn/trunk/html5.js
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-format.v1.min.js
https://d3js.org/d3-queue.v3.min.js
https://d3js.org/d3-selection.v1.min.js