block by jalapic a78a6a08a99a9e98feef6a310472cd14

Erdős–Rényi Force Directed Graph

Full Screen

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

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

index.html

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