简体   繁体   中英

Adding nodes to the existing clustered force layout in d3js

From two days I am trying to add new nodes to existing clustered force layout. I am able to add nodes to existing force and pack layout but gravity and charge force is not applying on newly added nodes and I think 'tick' event call back is also not occurring for newly added nodes. I have attached my code below.

var width = 960,
    height = 500,
    padding = 1.5, // separation between same-color nodes
    clusterPadding = 20, // separation between different-color nodes
    maxRadius = 12;

var n = 200, // total number of nodes
    m = 10; // number of distinct clusters

var color = d3.scale.category10()
    .domain(d3.range(m));

// The largest node for each cluster.
var clusters = new Array(m);

var nodes = d3.range(n).map(function() {
  var i = Math.floor(Math.random() * m),
      r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
      d = {cluster: i, radius: r};
  if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
  return d;
});

Use the pack layout to initialize node positions.
var pack = d3.layout.pack()
    .sort(null)
    .size([width, height])
    .children(function(d) { return d.values; })
    .value(function(d) { return d.radius * d.radius; })
    .nodes({values: d3.nest()
      .key(function(d) { return d.cluster; })
      .entries(nodes)});

var force = d3.layout.force()
    .nodes(nodes)
    .size([width, height])
    .gravity(0.01)
    .charge(function(d) {
      if(d.radius == clusters[d.cluster].radius) {
          return(-10 * d.radius);
      }
      else {
          return(0);
      }
    })
    .on("tick", tick)
    .start();

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

var node = svg.selectAll("circle")
    .data(nodes)
    .enter().append("circle")
    .style("fill", function(d) { return color(d.cluster); })
    .call(force.drag);

node.transition()
    .duration(750)
    .delay(function(d, i) { return i * 5; })
    .attrTween("r", function(d) {
      var i = d3.interpolate(0, d.radius);
      return function(t) { return d.radius = i(t); };
    });

//This setInterval function is for adding new node to existing 
//force and pack layout for every one second
setInterval(function() {

  var i = Math.floor(Math.random() * m),
      r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
      d = {cluster: i, radius: r, depth: 2};
      if(d.radius < clusters[d.cluster].radius ) {
        nodes.push(d);
      }

      force.nodes(nodes).start();
      var node = svg.selectAll("circle")
         .data(nodes)
         .enter().append("circle")
         .style("fill", function(d) { return color(d.cluster); })
         .attr({r: function(d) { return(d.radius); },
                cx: function(d) { return(d.x); },
                cy: function(d) { return(d.y); },
              })
         .call(force.drag);

}, 1000);

function tick(e) {
  node
      .each(cluster(e.alpha * 0.1))
      .each(collide(e.alpha * 0.3))
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; });
}

// Move d to be adjacent to the cluster node.
function cluster(alpha) {
  return function(d) {
    var cluster = clusters[d.cluster];
    if (cluster === d) return;
    var x = d.x - cluster.x,
        y = d.y - cluster.y,
        l = Math.sqrt(x * x + y * y),
        r = d.radius + cluster.radius + 10;
    if (l != r) {
      l = (l - r) / l * alpha;
      d.x -= x *= l;
      d.y -= y *= l;
      cluster.x += x;
      cluster.y += y;
    }
  };
}

// Resolves collisions between d and all other circles.
function collide(alpha) {
  var quadtree = d3.geom.quadtree(nodes);
  return function(d) {
    var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
        nx1 = d.x - r,
        nx2 = d.x + r,
        ny1 = d.y - r,
        ny2 = d.y + r;
    quadtree.visit(function(quad, x1, y1, x2, y2) {
      if (quad.point && (quad.point !== d)) {
        var x = d.x - quad.point.x,
            y = d.y - quad.point.y,
            l = Math.sqrt(x * x + y * y),
            r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
        if (l < r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          quad.point.x += x;
          quad.point.y += y;
        }
      }
      return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
    });
  };
}

I have gone through various documentation from two days and tried everything and I could not able to understand what is going on inside my JavaScript code. Finally I end up here. Please some one help me out.

You were not managing the general update pattern properly.
Here is the correct way to do it..

setInterval(function() {

  var i = Math.floor(Math.random() * m),
    r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
    d = {cluster: i, radius: r, depth: 2};
  if(d.radius < clusters[d.cluster].radius ) {
    nodes.push(d);
  }

  node = node.data(nodes);
  node.enter().append("circle")
    .style("fill", function(d) { return color(d.cluster); })
    .attr({r: function(d) { return(d.radius); },
      cx: function(d) { return(d.x); },
      cy: function(d) { return(d.y); },
    })
    .call(force.drag);
  force.start();

}, 1000);

And here is a working version...

  function drawAnimation() { var width = 960, height = 500, padding = 1.5, // separation between same-color nodes clusterPadding = 20, // separation between different-color nodes maxRadius = 12; var n = 200, // total number of nodes m = 10; // number of distinct clusters var color = d3.scale.category10() .domain(d3.range(m)); // The largest node for each cluster. var clusters = new Array(m); var nodes = d3.range(n).map(function() { var i = Math.floor(Math.random() * m), r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, d = {cluster: i, radius: r}; if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d; return d; }); // Use the pack layout to initialize node positions. var pack = d3.layout.pack() .sort(null) .size([width, height]) .children(function(d) { return d.values; }) .value(function(d) { return d.radius * d.radius; }) .nodes({values: d3.nest() .key(function(d) { return d.cluster; }) .entries(nodes)}); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(0.01) .charge(function(d) { if(d.radius == clusters[d.cluster].radius) { return(-10 * d.radius); } else { return(0); } }) .on("tick", tick) .start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var node = svg.selectAll("circle") .data(nodes) .enter().append("circle") .style("fill", function(d) { return color(d.cluster); }) .call(force.drag); node.transition() .duration(750) .delay(function(d, i) { return i * 5; }) .attrTween("r", function(d) { var i = d3.interpolate(0, d.radius); return function(t) { return d.radius = i(t); }; }); setInterval(function() { var i = Math.floor(Math.random() * m), r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, d = {cluster: i, radius: r, depth: 2}; if(d.radius < clusters[d.cluster].radius ) { nodes.push(d); } node = node.data(nodes); node.enter().append("circle") .style("fill", function(d) { return color(d.cluster); }) .attr({r: function(d) { return(d.radius); }, cx: function(d) { return(dx); }, cy: function(d) { return(dy); }, }) .call(force.drag); force.start(); }, 1000); function tick(e) { node .each(cluster(e.alpha * 0.1)) .each(collide(e.alpha * 0.3)) .attr("cx", function(d) { return dx; }) .attr("cy", function(d) { return dy; }); } // Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.cluster]; if (cluster === d) return; var x = dx - cluster.x, y = dy - cluster.y, l = Math.sqrt(x * x + y * y), r = d.radius + cluster.radius + 10; if (l != r) { l = (l - r) / l * alpha; dx -= x *= l; dy -= y *= l; cluster.x += x; cluster.y += y; } }; } // Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + Math.max(padding, clusterPadding), nx1 = dx - r, nx2 = dx + r, ny1 = dy - r, ny2 = dy + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = dx - quad.point.x, y = dy - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding); if (l < r) { l = (l - r) / l * alpha; dx -= x *= l; dy -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } } drawAnimation(); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM