Skip to content

Instantly share code, notes, and snippets.

@trvrb
Last active February 21, 2020 01:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trvrb/6e4f9245903e4b25aec4a8d5c6401a4e to your computer and use it in GitHub Desktop.
Save trvrb/6e4f9245903e4b25aec4a8d5c6401a4e to your computer and use it in GitHub Desktop.
CEIRR Network
{
"nodes": [
{
"name": "Bedford"
},
{
"name": "Bloom"
},
{
"name": "Cobey"
},
{
"name": "Lakdawala"
},
{
"name": "Boyd"
},
{
"name": "Cherry"
},
{
"name": "Hensley"
},
{
"name": "Weissman"
},
{
"name": "Worobey"
},
{
"name": "Lauring"
},
{
"name": "Martin"
},
{
"name": "Monto"
},
{
"name": "Cowling"
},
{
"name": "Lewis"
},
{
"name": "Gauger"
},
{
"name": "Vincent"
},
{
"name": "Wherry"
},
{
"name": "Betts"
},
{
"name": "Ward"
},
{
"name": "Wilson"
}
],
"categories": [
{
"name": "Papers",
"color": "#FFC300",
"weight": 0.2
},
{
"name": "Grants",
"color": "#476091",
"weight": 1
}
],
"links": [
[
[
0,
0
],
[
1.2,
2
],
[
0.8,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
]
],
[
[
1.2,
2
],
[
0,
0
],
[
0.2,
1
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
1.0,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.6000000000000001,
1
]
],
[
[
0.8,
0
],
[
0.2,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.2,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
1
],
[
0.2,
1
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
2
]
],
[
[
0.2,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
1
],
[
0,
0
],
[
0.2,
2
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
1
],
[
0,
0
],
[
0,
0
],
[
0,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
2
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0.2,
0
],
[
1.0,
1
],
[
1.2,
1
],
[
0.2,
2
],
[
0,
1
],
[
0.2,
2
],
[
0,
0
],
[
0.8,
0
],
[
0.2,
0
],
[
0,
0
],
[
0.8,
1
],
[
1.0,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.8,
1
],
[
0,
0
],
[
0,
0
],
[
0.8,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.8,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.7999999999999998,
3
],
[
1.9999999999999998,
2
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0.2,
0
],
[
0.2,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.8,
1
],
[
0,
0
],
[
0,
0
],
[
1.7999999999999998,
3
],
[
0,
0
],
[
6.600000000000003,
3
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0.2,
0
],
[
0.2,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.0,
1
],
[
0,
0
],
[
0,
0
],
[
1.9999999999999998,
2
],
[
6.600000000000003,
3
],
[
0,
0
],
[
1.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.4,
0
],
[
2.8000000000000003,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.4,
0
],
[
0,
0
],
[
2.8000000000000003,
3
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
2.8000000000000003,
1
],
[
2.8000000000000003,
3
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0.8,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.7999999999999998,
1
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
1.7999999999999998,
1
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
[
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
10,
0
]
],
[
[
0.2,
0
],
[
0.6000000000000001,
1
],
[
0,
2
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0.8,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0.2,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
10,
0
],
[
0,
0
]
]
]
}
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-path')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-path'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3));
}(this, function (exports,d3Array,d3Path) { 'use strict';
var cos = Math.cos;
var sin = Math.sin;
var pi = Math.PI;
var halfPi = pi / 2;
var tau = pi * 2;
var max = Math.max;
function compareValue(compare) {
return function(a, b) {
return compare(
a.source.value + a.target.value,
b.source.value + b.target.value
);
};
}
function multichord() {
var padAngle = 0,
sortGroups = null,
sortSubgroups = null,
sortChords = null;
function multichord(matrix) {
var n = matrix.length,
nCategories = matrix[0][0].length,
groupSums = {},
groupIndex = d3Array.range(n),
subgroupIndex = [],
chords = [],
groups = chords.groups = new Array(n),
subgroups = chords.subgroups = new Array(n * n),
z,
k,
x,
x0,
dx,
i,
j;
// Compute the sum.
z = 0, i = -1; while (++i < n) {
if (!groupSums[i]){
groupSums[i] = {}
}
x = 0, j = -1; while (++j < n) {
if (!groupSums[j]){
groupSums[j] = {}
}
x += d3Array.sum(matrix[i][j])
if (!groupSums[i].in){
groupSums[i].in = d3Array.sum(matrix[i][j])
} else {
groupSums[i].in += d3Array.sum(matrix[i][j])
}
if (!groupSums[j].out){
groupSums[j].out = d3Array.sum(matrix[i][j])
} else {
groupSums[j].out += d3Array.sum(matrix[i][j])
}
}
subgroupIndex.push(d3Array.range(n));
z += x;
}
// Sort groups…
if (sortGroups) groupIndex.sort(function(a, b) {
return sortGroups(groupSums[a].in, groupSums[b].in);
});
// Sort subgroups…
if (sortSubgroups) subgroupIndex.forEach(function(d, i) {
d.sort(function(a, b) {
return sortSubgroups(d3Array.sum(matrix[i][a]), d3Array.sum(matrix[i][b]));
});
});
// Convert the sum to scaling factor for [0, 2pi].
// TODO Allow start and end angle to be specified?
// TODO Allow padding to be specified as percentage?
z = max(0, tau - padAngle * n) / z;
dx = z ? padAngle : tau / n;
// Compute the start and end angle for each group and subgroup.
// Note: Opera has a bug reordering object literal properties!
x = 0, i = -1; while (++i < n) {
x0 = x, j = -1; while (++j < n) {
var di = groupIndex[i],
dj = subgroupIndex[di][j],
v = d3Array.sum(matrix[di][dj]),
a0 = x;
x += v * z;
subgroups[dj * n + di] = new Array(nCategories), k = -1; while (++k < nCategories) {
v = matrix[di][dj][k];
var b0 = a0,
b1 = a0 += v * z;
subgroups[dj * n + di][k] = {
index: di,
subindex: dj,
startAngle: b0,
endAngle: b1,
value: v,
category: k,
};
};
}
groups[di] = {
index: di,
startAngle: x0,
endAngle: x,
value: {in: groupSums[di].in,
out: groupSums[di].out}
};
x += dx;
}
// Generate chords for each (non-empty) subgroup-subgroup link.
i = -1; while (++i < n) {
j = i - 1; while (++j < n) {
k = -1; while (++k < nCategories) {
var source = subgroups[j * n + i][k],
target = subgroups[i * n + j][k];
if (source.value || target.value) {
chords.push(source.value < target.value
? {source: target, target: source}
: {source: source, target: target});
}
}
}
}
return sortChords ? chords.sort(sortChords) : chords;
}
multichord.padAngle = function(_) {
return arguments.length ? (padAngle = max(0, _), multichord) : padAngle;
};
multichord.sortGroups = function(_) {
return arguments.length ? (sortGroups = _, multichord) : sortGroups;
};
multichord.sortSubgroups = function(_) {
return arguments.length ? (sortSubgroups = _, multichord) : sortSubgroups;
};
multichord.sortChords = function(_) {
return arguments.length ? (_ == null ? sortChords = null : (sortChords = compareValue(_))._ = _, multichord) : sortChords && sortChords._;
};
return multichord;
}
var slice = Array.prototype.slice;
function constant(x) {
return function() {
return x;
};
}
function defaultSource(d) {
return d.source;
}
function defaultTarget(d) {
return d.target;
}
function defaultRadius(d) {
return d.radius;
}
function defaultStartAngle(d) {
return d.startAngle;
}
function defaultEndAngle(d) {
return d.endAngle;
}
function ribbon() {
var source = defaultSource,
target = defaultTarget,
radius = defaultRadius,
startAngle = defaultStartAngle,
endAngle = defaultEndAngle,
context = null;
function ribbon() {
var buffer,
argv = slice.call(arguments),
s = source.apply(this, argv),
t = target.apply(this, argv),
sr = +radius.apply(this, (argv[0] = s, argv)),
sa0 = startAngle.apply(this, argv) - halfPi,
sa1 = endAngle.apply(this, argv) - halfPi,
sx0 = sr * cos(sa0),
sy0 = sr * sin(sa0),
tr = +radius.apply(this, (argv[0] = t, argv)),
ta0 = startAngle.apply(this, argv) - halfPi,
ta1 = endAngle.apply(this, argv) - halfPi;
if (!context) context = buffer = d3Path.path();
context.moveTo(sx0, sy0);
context.arc(0, 0, sr, sa0, sa1);
if (sa0 !== ta0 || sa1 !== ta1) { // TODO sr !== tr?
context.quadraticCurveTo(0, 0, tr * cos(ta0), tr * sin(ta0));
context.arc(0, 0, tr, ta0, ta1);
}
context.quadraticCurveTo(0, 0, sx0, sy0);
context.closePath();
if (buffer) return context = null, buffer + "" || null;
}
ribbon.radius = function(_) {
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), ribbon) : radius;
};
ribbon.startAngle = function(_) {
return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant(+_), ribbon) : startAngle;
};
ribbon.endAngle = function(_) {
return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant(+_), ribbon) : endAngle;
};
ribbon.source = function(_) {
return arguments.length ? (source = _, ribbon) : source;
};
ribbon.target = function(_) {
return arguments.length ? (target = _, ribbon) : target;
};
ribbon.context = function(_) {
return arguments.length ? ((context = _ == null ? null : _), ribbon) : context;
};
return ribbon;
}
exports.multichord = multichord;
exports.ribbon = ribbon;
Object.defineProperty(exports, '__esModule', { value: true });
}));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Penn-CEIRR</title>
<meta name="author" content="Trevor Bedford">
<!-- 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;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 1;
}
</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 = 800,
WIDTH = 800;
outerRadius = Math.min(WIDTH, HEIGHT) / 2 - 40
innerRadius = outerRadius - 15;
// 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(",.1f")(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, "ceirr-data.json")
.await(ready);
function ready(error, data) {
if (error) throw error;
var nodes = data.nodes,
categories = data.categories;
links = data.links;
var chords = layout(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", "#AAA");
// .style("fill", function(d, i) { return nodes[i].color; });
// Add a text label.
groupText = group.append("text")
.attr("x", 1)
.attr("dy", -6)
.append("textPath")
.attr("xlink:href", function(d, i) { return "#group" + i; })
.text(function(d, i) { return nodes[i].name; })
.attr("font-family", "Helvetica")
.attr("opacity", function(d, i) {
// Hide labels that don't fit
if (groupPath._groups[0][i].getTotalLength() / 2 - 25 < this.getComputedTextLength()) {
return 1;
} 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 categories[d.source.category].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) {
var source = nodes[d.source.index];
var target = nodes[d.target.index];
var category = categories[d.source.category];
return category.name
+ "\n" + source.name
+ " → " + target.name
+ ": " + formatNumber(d.source.value * category.weight)
+ "\n" + target.name
+ " → " + source.name
+ ": " + formatNumber(d.target.value * category.weight);
});
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;
});
}
// legend
var legendRectSize = 24;
var legendSpacing = 6;
var legendLeftPadding = 60;
var legendTopPadding = 60;
var legend = svg.selectAll('.legend')
.data(categories)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * categories.length / 2;
var horz = legendLeftPadding + -2 * legendRectSize;
var vert = legendTopPadding + i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function(d) { return d.color; })
.style('stroke', function(d) { return "#555" });
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.attr("font-family", "Helvetica")
.attr("font-size", "14px")
.text(function(d) { return d.name; });
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment