Skip to content

Instantly share code, notes, and snippets.

@christophermanning
Created December 2, 2012 06:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christophermanning/4187201 to your computer and use it in GitHub Desktop.
Save christophermanning/4187201 to your computer and use it in GitHub Desktop.
Erdős–Rényi Force Directed Graph

Created by Christopher Manning

Summary

This creates a graph with the number of nodes you specify and random edges based on the probability selected. The number indicates how many edges are connected to that node. Red nodes are not connected to any other nodes.

Inspired by this tweet from @ChicagoCDO

Nodes are added to the graph along the path of an Archimedean spiral. This is more pleasing than adding nodes at random locations. This also prevents new nodes from chaotically bouncing around since now they are always placed at a reasonable distance from each other.

Controls

  • Use the mousewheel to increase or decrease the number of nodes.

References

<!DOCTYPE html>
<html>
<head>
<title>Erdős–Rényi Force Directed Graph</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
padding: 0
margin: 0
}
#texts text {
fill: #000;
font-weight: bold;
font-family: monospace;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
#lines line {
stroke: #999;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/2.10.0/d3.v2.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<script type="text/javascript">
var config = { "nodes" : 100, "probability" : 1, "linkDistance" : 30, "linkStrength": 0.001, "charge" : 35, "gravity" : .1 };
var gui = new dat.GUI();
var nodesChanger = gui.add(config, "nodes", 1, 200).listen().step(1);
nodesChanger.onChange(function(value) {
if(value != 200) {
erdosReni()
restart()
}
});
var probabilityChanger = gui.add(config, "probability", 0, 100);
probabilityChanger.onChange(function(value) {
erdosReni()
restart()
});
var fl = gui.addFolder('Force Layout');
var linkDistanceChanger = fl.add(config, "linkDistance", 0, 400);
linkDistanceChanger.onChange(function(value) {
force.linkDistance(value)
restart()
});
var linkStrengthChanger = fl.add(config, "linkStrength", 0, 1);
linkStrengthChanger.onChange(function(value) {
force.linkStrength(value)
restart()
});
var chargeChanger = fl.add(config,"charge", 0, 500);
chargeChanger.onChange(function(value) {
force.charge(-value)
restart()
});
var gravityChanger = fl.add(config,"gravity", 0, 1);
gravityChanger.onChange(function(value) {
force.gravity(value)
restart()
});
config.regenerate = function() {
erdosReni()
restart()
}
gui.add(config, 'regenerate')
var width = window.innerWidth-245,
height = window.innerHeight,
radius = 10,
maxLinks = 5000,
drawMax = null,
nodes = [],
links = [];
var zoom = d3.behavior.zoom()
.scale(config["nodes"])
.scaleExtent([1, 200])
.on("zoom", function(d,i) {
config["nodes"] = Math.ceil(d3.event.scale)
erdosReni()
restart()
});
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
lines = svg.append("g").attr("id", "lines")
texts = svg.append("g").attr("id", "texts")
var force = d3.layout.force()
.linkDistance(config["linkDistance"])
.linkStrength(config["linkStrength"])
.gravity(config["gravity"])
.size([width, height])
.charge(-config["charge"]);
erdosReni();
restart();
function erdosReni() {
numNodes = config["nodes"]
if(nodes.length < numNodes) {
for(i=nodes.length; numNodes>nodes.length; i++){
// http://en.wikipedia.org/wiki/Archimedean_spiral
angle = 2 * i;
nodes.push({x: angle*Math.cos(angle)+(width/2), y: angle*Math.sin(angle)+(height/2)});
}
} else if(nodes.length > numNodes) {
nodes.length = numNodes
}
links = []
linksIndex = {}
nodes.forEach(function(node, nodei) {
nodes.forEach(function(node2, node2i) {
//check the probabilty of an edge once and don't link to self
if (linksIndex[nodei + "," + node2i] || linksIndex[node2i + "," + nodei] || nodei == node2i) return
linksIndex[nodei + "," + node2i] = 1;
if (Math.random() < config["probability"] * .01) {
links.push({source: node, target: node2});
}
})
})
if(links.length > maxLinks) {
if(drawMax == true || drawMax == null && confirm("draw more than "+links.length+" edges?")) {
drawMax = true
} else {
links.length = maxLinks
drawMax = false
}
}
force.nodes(nodes).links(links)
}
function restart() {
force.start();
link = lines.selectAll("#lines line")
.data(links)
link.enter().insert("line")
.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; });
link.exit().remove()
node = texts.selectAll("#texts text")
.data(nodes)
node.enter().insert("text")
.call(force.drag);
node.text(function(d) { return d.weight; })
.style("fill", function(d) { return d.weight == 0 ? "darkred" : "black" })
node.exit().remove()
}
force.on("tick", function() {
svg.selectAll("#lines line")
.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; })
svg.selectAll("#texts text")
.attr("transform", function(d) {
return "translate("+d.x+"," + d.y+ ")"
})
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment