将节点添加到 d3js 中现有的集群力布局

Adding nodes to the existing clustered force layout in d3js

从这两天开始,我试图将新节点添加到现有的集群部队布局中。我能够将节点添加到现有力和包装布局,但重力和电荷力不适用于新添加的节点,我认为 'tick' 事件回调也不会发生在新添加的节点上。我在下面附上了我的代码。

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;
    });
  };
}

两天来我浏览了各种文档并尝试了所有方法,但我无法理解我的 JavaScript 代码中发生了什么。最后我结束了这里。请有人帮助我。

您没有正确管理 general update pattern
这是正确的方法..

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);

这是一个工作版本...

  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(d.x); },
          cy: function(d) { return(d.y); },
        })
        .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 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;
        });
      };
    }
  }
  drawAnimation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>