Skip to content

Instantly share code, notes, and snippets.

@willbreitkreutz
Forked from mbostock/.block
Last active August 29, 2015 14:25
Show Gist options
  • Save willbreitkreutz/637fce833bb621cb97e0 to your computer and use it in GitHub Desktop.
Save willbreitkreutz/637fce833bb621cb97e0 to your computer and use it in GitHub Desktop.
Hierarchical Edge Bundling

Mouseover any of the nodes in this network to see the incoming links (dependants) in green and the outgoing links (dependencies) in red.

Compare to the static version.

[{"name":"RADS II","size":1000,"imports":["Risk Tools","MMC ProjectWise","DSMMCX ProjectWise","Comment Database","EPPs","State of Levees Report"]},
{"name":"PM Tools","size":1000,"imports":["NLD","Bridge Tools","Schedule Database","DSPMT","Funding Request","Schedule Database"]},
{"name":"Bridge Tools","size":1000,"imports":["Risk Tools","NLD"]},
{"name":"Risk Tools","size":1000,"imports":["Bridge Tools","NLD"]},
{"name":"Dashboard","size":1000,"imports":["PM Tools","Bridge Tools","Risk Tools","LIS","Event Registration","MCRAM","Status Map","Funding Request","LSIP","Comment Database","MMC Data Viewer","SimDams"]},
{"name":"LIS","size":1000,"imports":["NLD","Levee Performance Data","Freeboard"]},
{"name":"NLD","size":1000,"imports":["LIS","LST","NSI","Levee Performance Data","Freeboard","Event Management Tools"]},
{"name":"LST","size":1000,"imports":["Risk Tools","LIS","NLD","NSI","Levee Performance Data","Freeboard"]},
{"name":"MCRAM","size":1000,"imports":["NSI"]},
{"name":"Event Registration","size":2000,"imports":["PM Tools","LST","Funding Request"]},
{"name":"Funding Request","size":1500,"imports":["PM Tools","NLD","LST","Event Registration"]},
{"name":"Status Map","size":1000,"imports":["NLD","LST","MMC Data Viewer"]},
{"name":"NSI","size":1000,"imports":[]},
{"name":"LSIP","size":1000,"imports":["Risk Tools","LIS","NLD","LST"]},
{"name":"Schedule Database","size":1000,"imports":["PM Tools","NLD","Funding Request","MMC Data Viewer"]},
{"name":"MMC ProjectWise","size":1000,"imports":["RADS II","NLD","LST","Schedule Database"]},
{"name":"DSMMCX ProjectWise","size":1000,"imports":["RADS II","LST"]},
{"name":"Comment Database","size":1000,"imports":["RADS II","Schedule Database","MMC ProjectWise"]},
{"name":"MMC Data Viewer","size":1000,"imports":["RADS II","Schedule Database","MMC ProjectWise","SimDams","DSPMT"]},
{"name":"408 Database","size":1000,"imports":["NLD"]},
{"name":"Levee Performance Data","size":1000,"imports":["NLD","Freeboard","Event Management Tools"]},
{"name":"Freeboard","size":1000,"imports":["NLD","NSI"]},
{"name":"EPPs","size":1000,"imports":["LIS","LST","NSI","Levee Performance Data"]},
{"name":"NLSP","size":1000,"imports":["LIS","LST","NSI"]},
{"name":"State of Levees Report","size":2000,"imports":["NLD","LST","NSI","Levee Performance Data"]},
{"name":"SimDams","size":1000,"imports":["Risk Tools","MMC Data Viewer","DSPMT","Event Management Tools"]},
{"name":"NID","size":1000,"imports":["NSI"]},
{"name":"DSPMT","size":1000,"imports":["NID","NSI","Event Management Tools"]},
{"name":"Event Management Tools","size":2000,"imports":["Bridge Tools","LIS","NSI"]}]
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #bbb;
}
.node:hover {
fill: #000;
}
.link {
stroke: steelblue;
stroke-opacity: .4;
fill: none;
pointer-events: none;
}
.node:hover,
.node--source,
.node--target {
font-weight: 700;
}
.node--source {
fill: #2ca02c;
}
.node--target {
fill: #d62728;
}
.link--source,
.link--target {
stroke-opacity: 1;
stroke-width: 2px;
}
.link--source {
stroke: #d62728;
}
.link--target {
stroke: #2ca02c;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var diameter = 960,
radius = diameter / 2,
innerRadius = radius - 100;
var cluster = d3.layout.cluster()
.size([360, innerRadius])
.sort(null)
.value(function(d) { return d.size; });
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var link = svg.append("g").selectAll(".link"),
node = svg.append("g").selectAll(".node");
d3.json("imports.json", function(error, classes) {
if (error) throw error;
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageImports(nodes);
link = link
.data(bundle(links))
.enter().append("path")
.each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
.attr("class", "link")
.attr("d", line);
node = node
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("text")
.attr("class", "node")
.attr("dy", ".31em")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
.style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.text(function(d) { return d.key; })
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);
});
function mouseovered(d) {
node
.each(function(n) { n.target = n.source = false; });
link
.classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
.classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
.filter(function(l) { return l.target === d || l.source === d; })
.each(function() { this.parentNode.appendChild(this); });
node
.classed("node--target", function(n) { return n.target; })
.classed("node--source", function(n) { return n.source; });
}
function mouseouted(d) {
link
.classed("link--target", false)
.classed("link--source", false);
node
.classed("node--target", false)
.classed("node--source", false);
}
d3.select(self.frameElement).style("height", diameter + "px");
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
return imports;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment