Created by Christopher Manning
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.
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++){
// //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>