Skip to content

Instantly share code, notes, and snippets.

@stv0g
Forked from mbostock/.block
Last active July 31, 2017 08:10
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 stv0g/d744d0799dc19c77db9f200017d9c113 to your computer and use it in GitHub Desktop.
Save stv0g/d744d0799dc19c77db9f200017d9c113 to your computer and use it in GitHub Desktop.
Force-Directed Graph
license: gpl-3.0
height: 600

This simple force-directed graph shows character co-occurence in Les Misérables. A physical simulation of charged particles and springs places related characters in closer proximity, while unrelated characters are farther apart. Layout algorithm inspired by Tim Dwyer and Thomas Jakobsen. Data based on character coappearence in Victor Hugo's Les Misérables, compiled by Donald Knuth.

Compare this display to a force layout with curved links, a force layout with fisheye distortion and a matrix diagram.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("miserables.json", function(error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
{
"nodes": [
{"id": "ACS", "group": 1},
{"id": "WIT", "group": 2},
{"id": "UPB", "group": 3},
{"id": "POLITO", "group": 4}
],
"links": [
{"source": "ACS", "target": "UPB", "value": 319 },
{"source": "ACS", "target": "POLITO", "value": 235 },
{"source": "ACS", "target": "WIT", "value": 477 },
{"source": "WIT", "target": "UPB", "value": 596 },
{"source": "WIT", "target": "POLITO", "value": 381 },
{"source": "POLITO", "target": "UPB", "value": 410 }
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment