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.
forked from mbostock's block: Force-Directed Graph
xxxxxxxxxx
<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; }));
////////////////////////////////////
//////////define positions//////////
////////////////////////////////////
var x1 = function(d) {
d3.selectAll("circle")
.each(function(e,i){
if(d.source == e.id){
x = d3.select(this).attr("cx");
}
})
return x;
};
var y1 = function(d) {
d3.selectAll("circle")
.each(function(e,i){
if(d.source == e.id){
y = d3.select(this).attr("cy");
}
})
return y;
};
var x2 = function(d) {
d3.selectAll("circle")
.each(function(e,i){
if(d.target == e.id){
x = d3.select(this).attr("cx");
}
})
return x;
};
var y2 = function(d) {
d3.selectAll("circle")
.each(function(e,i){
if(d.target == e.id){
y = d3.select(this).attr("cy");
}
})
return y;
};
var cx = function(d) { return Math.random() * width; };
var cy = function(d) { return Math.random() * height; };
////////////////////////////////////
/////////define algorithms//////////
////////////////////////////////////
// define distance of two nodes
var d = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
//logarithmic strength springs: the force exerted on a ring by a spring
1*Math.log(d/10)
//inverse square law force
2/Math.sqrt(d)
//custom force
function new_force() {
for (var i = 0, n = nodes.length; i < n; ++i) {
curr_node = nodes_data[i];
curr_node.x = cx;
curr_node.y = cy;
}
}
////////////////////////////////////
//////////drawing///////////////////
////////////////////////////////////
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);
node
.attr("cx", cx)
.attr("cy", cy);
link
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2);
});
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>
https://d3js.org/d3.v4.min.js