我应该如何删除 d3-force 中的节点?

How should I remove nodes in d3-force?

我想在点击这个节点时删除节点,所以我只是在点击nodes = nodes.filter(v => v.id !== d.id)时删除了节点。实际上,第一次可以,但是接下来就不行了。

这是我的代码:

uuid = function uuid() {
  var template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
  return template.replace(/[xy]/g, c => {
    var r = (Math.random() * 16) | 0;
    if (c === 'y') r = (r & 3) | 8;
    return r.toString(16);
  });
}

const width = 800;
const height = 400;
const count = 10;
let nodes = Array.from({length: count}).map((_, i) => ({x: width / count * i + 20, y: height / 2, id: uuid()}));
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg
    .selectAll(".node");


const simulation = d3
  .forceSimulation()
  .nodes(nodes)
  .force("center", d3.forceCenter(width / 2, height / 2))
  .stop();

function update() {  
  node = node.data(nodes, d=> d.id)
    .join("g")
    .append("circle")
    .attr("r", 12)
    .attr("cursor", "move")
    .attr("fill", "#ccc")
    .attr("stroke", "#000")
    .attr("stroke-width", "1.5px")
    .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";})
    .on("click", (ev, d) => {
      nodes = nodes.filter(v => v.id !== d.id);
      update();
  });
  
  simulation.nodes(nodes);

  if (simulation.alpha() <= 1) {
    simulation.alpha(1);
    simulation.restart();
  }
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>

我不知道问题出在哪里,但请查看您的代码生成的 DOM。 有一个g个元素的列表,每个g包含很多嵌套的circle。很奇怪,也说不通。 所以我建议你重构你的代码,使其具有类似的东西:

<svg>
  <circle class="node#1"... />
  <circle class="node#2"... />
  <circle class="node#3"... />
  <circle class="node#4"... />
</svg>

然后,当使用点击圆形元素时,获取 class 名称并删除该节点

您只想为新节点附加一个圆圈,对于任何其他节点,附加一个圆圈意味着您下次单击时,实际上有两个圆圈要删除,一个都没有!

.join() 可以传递三个函数,您可以将其应用于 enterupdateexit 选择。这些被执行,return 值被加入,然后你可以对所有选择组合进行操作。

在这种情况下,我将所有一次性 circle 逻辑移至 enter 部分,注意不要 return circle,而是 g 您首先附加的元素。如果您 return circle,它会将这些与现有的 `g 元素结合起来,逻辑就会中断。

uuid = function uuid() {
  var template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
  return template.replace(/[xy]/g, c => {
    var r = (Math.random() * 16) | 0;
    if (c === 'y') r = (r & 3) | 8;
    return r.toString(16);
  });
}

const width = 800;
const height = 400;
const count = 10;
let nodes = Array.from({length: count}).map((_, i) => ({x: width / count * i + 20, y: height / 2, id: uuid()}));
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg
    .selectAll(".node");

const simulation = d3
  .forceSimulation()
  .nodes(nodes)
  .force("center", d3.forceCenter(width / 2, height / 2))
  .stop();

function update() {  
  console.log(nodes.length);
  node = node.data(nodes, d=> d.id)
    .join(
      enter => {
        const g = enter
          .append("g")
          .attr("class", "node");
        g
          .append("circle")
          .attr("r", 12)
          .attr("cursor", "move")
          .attr("fill", "#ccc")
          .attr("stroke", "#000")
          .attr("stroke-width", "1.5px");
        return g;
      },
      update => update,
      exit => exit.remove()
    )
    .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";})
    .on("click", (ev, d) => {
      nodes = nodes.filter(v => v.id !== d.id);
      update();
  });
  
  simulation.nodes(nodes);

  if (simulation.alpha() <= 1) {
    simulation.alpha(1);
    simulation.restart();
  }
}

update()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>