Skip to content

Instantly share code, notes, and snippets.

@jalapic
Created January 6, 2017 03:19
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 jalapic/a78a6a08a99a9e98feef6a310472cd14 to your computer and use it in GitHub Desktop.
Save jalapic/a78a6a08a99a9e98feef6a310472cd14 to your computer and use it in GitHub Desktop.
Erdős–Rényi Force Directed Graph
license: mit

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

forked from christophermanning's block: Erdős–Rényi Force Directed Graph

forked from FrissAnalytics's block: Erdős–Rényi Force Directed Graph

<!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