block by enjalot 7402f55e19e1e609f014

Node splitting

Full Screen

Mouseover to repel nodes. Adapted from my talk on force layouts. Compare to the canvas version.

forked from mbostock‘s block: Collision Detection

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<style>
  line.link {
    pointer-events: none;
  }
</style>
<script>

var width = 960,
    height = 500;


var circles = d3.range(10).map(function(i) { 
  return {
    radius: Math.random() * 12 + 8,
    index: i,
    radius: 15,
    group: Math.floor((i)/4) % 4
  }; 
})
var nodes = [{index: -1, group: -1, radius: 200 }].concat(circles)

//var circles = nodes.slice(1)
var links = [
  {source: circles[0], target: circles[1], type: "bond"},
  {source: circles[1], target: circles[2], type: "bond"},
  {source: circles[2], target: circles[3], type: "bond"},
  {source: circles[3], target: circles[0], type: "bond"},
];
  
var root = nodes[0];
var color = d3.scale.category10();

root.radius = 0;
root.fixed = true;

var force = d3.layout.force()
    .gravity(0.05)
    //.charge(function(d, i) { return i ? 0 : -200; })
    //.linkStrength(function(l, i) { return l.strength || 0 })
		//.linkStrength(0.5)
    //.linkDistance(10)
    .nodes(nodes)
    .size([width, height]);

force.start();
  

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.selectAll("circle")
    .data(circles)
  .enter().append("circle").classed("node", true)
    .attr("r", function(d) { return d.radius; })
    .style("fill", function(d, i) { return color(d.group); })
  .on("mouseout", splitNode)
.call(force.drag)

function splitNode(d) {
  var nodes = force.nodes()
  console.log("d", d)
  
  var index = nodes.length;
  var jiggle = 5 + Math.random() * 10;
 
  var newNode = {
    index: index,
    radius: Math.random() * 12 + 8,
    group: d.group,
    x: d.x + jiggle,
    y: d.y + jiggle,
    px: d.px + jiggle,
    py: d.py + jiggle
  }
  nodes.push(newNode)
  
  var newLink = {
    source: d, target: newNode, type: "bond"
  }
  links.push(newLink)
  
  svg.append("circle")
  .datum(newNode)
    .classed("node", true)
    .style("fill", function(d, i) { return color(d.group); })
  .call(force.drag)
  .on("mouseout", splitNode)

  
  svg.append("line")
  .datum(newLink)
  .classed("link", true)
  .attr({
  stroke: "#111"
})
  
}
  
svg.selectAll("line")
  .data(links)
.enter().append("line").classed("link", true)
.attr({
  stroke: "#111"
})

force.on("tick", function(e) {
  var nodes = force.nodes()

  // collision detection
  var q = d3.geom.quadtree(nodes);
  nodes.forEach(function(node) {
    q.visit(collide(node))
  })

  // strongly coupling certain nodes
  links.forEach(function(link) {
    var source = link.source;
    var target = link.target;
    if(link.type == "bond") {
      var x = source.x - target.x;
      var y = source.y - target.y;
      var dist = Math.sqrt(x * x + y * y);
      var r = source.radius + target.radius;
      
      if (dist < r) {
        dist = (dist - r) / dist * .5; //don't quite understand this
        source.x -= x *= dist;
        source.y -= y *= dist;
        target.x += x;
        target.y += y;
      } else {
        dist = -0.021; // not sure how to do the opposive of above
        source.x += x *= dist;
        source.y += y *= dist;
        target.x -= x;
        target.y -= y;
      }
    }
    link.strength = 1;
  })


  svg.selectAll("circle.node")
  .attr({
    cx: function(d) {return d.x },
    cy: function(d) { return d.y },
    r: function(d) { return d.radius }
  })
   
  svg.selectAll("line.link")
  .attr({
    x1: function(d) { return d.source.x },
    y1: function(d) { return d.source.y },
    x2: function(d) { return d.target.x },
    y2: function(d) { return d.target.y },
  })
});
  


svg.on("mousemove", function() {
  //var p1 = d3.mouse(this);
  //root.px = p1[0];
  //root.py = p1[1];
  force.resume();
});

function collide(node) {
  var r = node.radius + 16,
      nx1 = node.x - r,
      nx2 = node.x + r,
      ny1 = node.y - r,
      ny2 = node.y + r;
  return function(quad, x1, y1, x2, y2) {
    if (quad.point && (quad.point !== node)) {
      var x = node.x - quad.point.x,
          y = node.y - quad.point.y,
          dist = Math.sqrt(x * x + y * y),
          r = node.radius + quad.point.radius;
      if (dist < r) {
        dist = (dist - r) / dist * .5;
        node.x -= x *= dist;
        node.y -= y *= dist;
        quad.point.x += x;
        quad.point.y += y;
      }
    }
    return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
  };
}

</script>