简体   繁体   中英

Removing two nodes from a Force Layout fails while one succeeds

I'm trying to remove some nodes from a Force Layout.

The first part of the process is selecting one or more nodes. That's done with the click handler:

  var nodeg = node.enter().append("g")
    .attr("class", "node")
    .on('click', function (n) {
      if (n.dragging === true) {
        return;
      }

      // select the clicked node
      n.selected = !n.selected;

      d3.select(this)
        .transition()
        .duration(500)
        .ease('bounce')
        .attr('fill', getNodeBackground(n))
        .attr('transform', getNodeTransform(n));
    })
    .call(drag);

Note that I'm setting selected to true.

Next, if the user presses the delete key I remove the selected nodes:

function removeSelectedNodes() {
  if (!confirm('Are you sure you want to delete the selected relationships?')) {
    return;
  }

  var firstIndex = -1;
  d3.selectAll('.node')
    .each(function (n, i) {
      if (n.selected !== true) {
        return;
      }

      n.data.remove = true;
      n.data.index = i;

      if (firstIndex === -1) {
        firstIndex = i;
      }
    });

  var offset = 0;
  _.each(_.where(scope.nodes, {data: {remove: true}}), function (n) {
    var removeAt = n.index;
    if (n.index > firstIndex) {
      removeAt = n.index - 1 - offset;
      offset++;
    }

    scope.nodes.splice(removeAt, 1);
    scope.links.splice(removeAt - 1, 1);
  });

  renderGraph();
}

The entire renderGraph function looks like this:

function renderGraph() {
  force
    .nodes(scope.nodes)
    .links(scope.links)
    .start();

  var link = svg.selectAll(".link")
    .data(scope.links);
  link.exit().remove();

  link.enter().append("line")
    .attr("class", "link");

  var node = svg.selectAll(".node")
    .data(scope.nodes);
  node.exit().remove();

  var nodeg = node.enter().append("g")
    .attr("class", "node")
    .on('click', function (n) {
      if (n.dragging === true) {
        return;
      }

      // select the clicked node
      n.selected = !n.selected;

      d3.select(this)
        .transition()
        .duration(500)
        .ease('bounce')
        .attr('fill', getNodeBackground(n))
        .attr('transform', getNodeTransform(n));
    })
    .call(drag);

  nodeg.append("image")
    .attr("xlink:href", function (d) {
      return d.avatar || 'https://github.com/favicon.ico'
    })
    .attr("x", -56)
    .attr("y", -8)
    .attr("width", 64)
    .attr("height", 64);

  nodeg.append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .attr('class', 'name')
    .text(function (d) {
      return d.displayName;
    });

  nodeg.append("text")
    .attr("dx", 12)
    .attr("dy", "1.35em")
    .text(function (d) {
      return d.relationship;
    });

  force.on("tick", function () {
    link.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;
      });

    node.attr("transform", function (d) {
      return getNodeTransform(d);
    });
  });
}

This is where it starts going south. If I select one node , the graph is rendered correctly after splicing the node. However, if I remove two nodes the graph ends up persisting the first removed node (by index).

Let's say the first node (by index) was 'Bob' and the second node was 'Bill'. The second node will be removed, but the first one will persist. Interestingly, another one of the nodes, the current last node (by index), will be gone instead.

NOTE: the array looks good. The nodes I wanted removed are gone, and the remaining ones are correct.

What did I do wrong here?

UPDATE: I've tried not setting nodes and links after removing nodes, and just calling start :

force.start()

This didn't work.

The answer, thanks to Lars again, was to add a key function to the links and nodes:

  var link = svg.selectAll(".link")
    .data(scope.links, function (d) {
      return d.source.data._id + '|' + d.target.data._id;
    });

  var node = svg.selectAll(".node")
    .data(scope.nodes, function (d) {
      return d.data._id;
    });

Thanks Lars!

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