|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
|
|
<head> |
|
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> |
|
</head> |
|
<style> |
|
#vis { margin: 0 auto; width: 800px; } |
|
.group text { |
|
font: 9px sans-serif; |
|
pointer-events: none; |
|
} |
|
|
|
.group path { |
|
stroke: #000; |
|
} |
|
|
|
path.chord { |
|
stroke-width: .75; |
|
fill-opacity: .75; |
|
} |
|
|
|
#circle:hover path.fade { |
|
display: none; |
|
} |
|
|
|
</style> |
|
<body> |
|
|
|
<div id="vis"> |
|
|
|
</div> |
|
|
|
<script src="//d3js.org/d3.v3.min.js"></script> |
|
<script> |
|
|
|
var width = 800, |
|
height = 800, |
|
outerRadius = Math.min(width, height) / 2 - 4, |
|
innerRadius = outerRadius - 20, |
|
fill = d3.scale.category20(); |
|
|
|
var format = d3.format(",.3r"); |
|
|
|
// Square matrices, asynchronously loaded; credits is the transpose of commonSpeciesMatrix. |
|
var commonSpeciesMatrix = []; |
|
|
|
// The chord layout, for computing the angles of chords and groups. |
|
var layout = d3.layout.chord() |
|
.sortGroups(d3.descending) |
|
.sortSubgroups(d3.descending) |
|
.sortChords(d3.descending) |
|
.padding(.04); |
|
|
|
// The arc generator, for the groups. |
|
var arc = d3.svg.arc() |
|
.innerRadius(innerRadius) |
|
.outerRadius(outerRadius); |
|
|
|
// The chord generator (quadratic Bézier), for the chords. |
|
var chord = d3.svg.chord() |
|
.radius(innerRadius); |
|
|
|
// Load our data file… |
|
d3.csv("cnc-common-species.csv", type, function(error, data) { |
|
if (error) throw error; |
|
|
|
// Add an SVG element for each diagram, and translate the origin to the center. |
|
var svg = d3.select("#vis").selectAll("div.vis-container") |
|
.data([commonSpeciesMatrix]) |
|
.enter().append("div") |
|
.style("display", "inline-block") |
|
.style("width", width + "px") |
|
.style("height", height + "px") |
|
.append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.append("g") |
|
.attr("id", "circle") |
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); |
|
|
|
function mouseover( d, i ) { |
|
chordPaths.classed("fade", function(p) { |
|
return p.source.index != i |
|
&& p.target.index != i; |
|
}); |
|
chordPaths |
|
.style("fill", d2 => fill( d2.source.index === i ? d2.target.index : d2.source.index ) ) |
|
} |
|
|
|
var countryByName = d3.map(), |
|
countryIndex = -1, |
|
countryByIndex = []; |
|
|
|
// Compute a unique index for each country. |
|
data.forEach(function(d) { |
|
if (countryByName.has(d.city1)) d.city1 = countryByName.get(d.city1); |
|
else countryByName.set(d.city1, d.city1 = {name: d.city1, index: ++countryIndex}); |
|
if (countryByName.has(d.city2)) d.city2 = countryByName.get(d.city2); |
|
else countryByName.set(d.city2, d.city2 = {name: d.city2, index: ++countryIndex}); |
|
d.city2.risk = 1; //d.risk; |
|
}); |
|
|
|
// Initialize a square matrix of commonSpeciesMatrix and credits. |
|
for (var i = 0; i <= countryIndex; i++) { |
|
commonSpeciesMatrix[i] = []; |
|
// credits[i] = []; |
|
for (var j = 0; j <= countryIndex; j++) { |
|
commonSpeciesMatrix[i][j] = 0; |
|
// credits[i][j] = 0; |
|
} |
|
} |
|
|
|
// Populate the matrices, and stash a map from index to country. |
|
data.forEach(function(d) { |
|
commonSpeciesMatrix[d.city1.index][d.city2.index] = d; |
|
// credits[d.city2.index][d.city1.index] = d; |
|
countryByIndex[d.city1.index] = d.city1; |
|
countryByIndex[d.city2.index] = d.city2; |
|
}); |
|
|
|
// For each diagram… |
|
// svg.each(function(matrix, j) { |
|
// var svg = d3.select(this); |
|
|
|
// Compute the chord layout. |
|
layout.matrix(commonSpeciesMatrix); |
|
|
|
// Add chords. |
|
var chordPaths = svg.selectAll(".chord") |
|
.data(layout.chords) |
|
.enter().append("path") |
|
.attr("class", "chord") |
|
.style("fill", function(d) { return "#ccc"; }) |
|
.style("stroke", function(d) { return "#aaa"; }) |
|
.attr("d", chord) |
|
chordPaths |
|
.append("title") |
|
.text( d => `${d.source.value.city2.name} shared ${d.source.value.num_species} species with ${d.source.value.city1.name}` ); |
|
// .text(function(d) { return d.source.value.city2.name + " owes " + d.source.value.city1.name + " $" + format(d.source.value) + "B."; }); |
|
|
|
// Add groups. |
|
var g = svg.selectAll(".group") |
|
.data(layout.groups) |
|
.enter().append("g") |
|
.attr("class", "group"); |
|
|
|
// Add the group arc. |
|
g.append("path") |
|
.style("fill", function(d) { return fill(d.index); }) |
|
.attr("id", function(d, i) { return "group" + d.index + "-" + j; }) |
|
.attr("d", arc) |
|
.on("mouseover", mouseover) |
|
.on("mouseover", mouseover) |
|
.append("title") |
|
// .text(function(d) { return countryByIndex[d.index].name + " " + (j ? "owes" : "is owed") + " $" + format(d.value) + "B."; }); |
|
.text( d => countryByIndex[d.index].name ) |
|
|
|
// Add the group label (but only for large groups, where it will fit). |
|
// An alternative labeling mechanism would be nice for the small groups. |
|
g.append("text") |
|
.attr("x", 6) |
|
.attr("dy", 15) |
|
.filter(function(d) { return d.value > 110; }) |
|
.append("textPath") |
|
.attr("xlink:href", function(d) { return "#group" + d.index + "-" + j; }) |
|
.text(function(d) { return countryByIndex[d.index].name; }); |
|
// }); |
|
}); |
|
|
|
function type(d) { |
|
d.city1 = d.city1.replace( /Minneapolis/, 'Minn.' ); |
|
d.city2 = d.city2.replace( /Minneapolis/, 'Minn.' ); |
|
d.city1 = d.city1.replace( /Twin Ports/, 'TP' ); |
|
d.city2 = d.city2.replace( /Twin Ports/, 'TP' ); |
|
d.city1 = d.city1.replace( /The Wasatch Front/, 'Wasatch' ); |
|
d.city2 = d.city2.replace( /The Wasatch Front/, 'Wasatch' ); |
|
d.num_species = +d.num_species; |
|
d.risk = 1; |
|
d.valueOf = value; // for chord layout |
|
return d; |
|
} |
|
|
|
function value() { |
|
return this.num_species; |
|
} |
|
|
|
</script> |
|
</body> |