d3 enter()/ merge()创建副本而不是更新

时间:2017-07-18 12:41:08

标签: javascript d3.js

我使用D3.js(v4)构建交互式图形,其中每个父节点都有一组可折叠的子节点。因为我还想创建工具提示等,所以我用g包裹每个圆圈。我的代码的相关部分如下所示:

var node = svg.append("g").selectAll(".node");

...

function update() {

    nodes = getVisibleNodes()

    ...

    node = node.data(nodes, function(d) { return d.name; });
    node.exit().remove();
    node = node.enter()
        .append("g")
        .classed("node", true)
        .merge(node);

    node
        .append("circle")
        .attr("r", function(d) { return d.size / 500; })
        .on("click", click)
        .on("contextmenu", rightclick)
        .call(drag);
}

起初一切似乎都运行良好,但后来我注意到每次运行update并显示/隐藏一些节点时,新的输入节点会附加在旧节点之上。如果我点击一下,我会在每个g中找到数十个圆圈,一个在另一个上面绘制(当它们半透明时,它很容易看到)。

我真的不明白这里发生了什么,我做错了什么。对我有什么好建议吗?

1 个答案:

答案 0 :(得分:2)

只需浏览您的代码,我们就可以看到您有群组的“更新”/“输入”/“退出”选项,但圈子没有

因此,如果某个组既不在“输入”中也不在“退出”选项中,这意味着它处于“更新”选项中,那么您将向其添加圈子每次运行update函数。

这是一个非常基本的演示来展示它。这是错误的代码:data这是一个最多包含10个元素的随机数组。但是,正如您所看到的,有时圆圈的数量超过10:

var svg = d3.select("svg");
d3.select("button").on("click", update);
var color = d3.scaleOrdinal(d3.schemeCategory20)

function update() {
  var data = d3.range(~~(Math.random() * 10)).map(function(d) {
    return {
      size: ~~(Math.random() * 20),
      x: ~~(Math.random() * 300),
      y: ~~(Math.random() * 150)
    }
  })
  var node = svg.selectAll(".node").data(data);
  node.exit().remove();
  node.enter()
    .append("g")
    .classed("node", true)
    .merge(node)
    .append("circle")
    .attr("fill", function(d) {
      return color(d.size)
    })
    .attr("r", function(d) {
      return d.size;
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Update</button>
<br>
<svg></svg>

解决方案:

请勿使用merge,将您的输入和更新选项分开。并且,在更新选择中,选择现有圈子。

node.select("circle")
    .attr("r", etc...

以下是正确代码的演示,它永远不会超过10个圈子:

var svg = d3.select("svg");
d3.select("button").on("click", update);
var color = d3.scaleOrdinal(d3.schemeCategory20)

function update() {
  var data = d3.range(~~(Math.random() * 10)).map(function(d) {
    return {
      size: ~~(Math.random() * 20),
      x: ~~(Math.random() * 300),
      y: ~~(Math.random() * 150)
    }
  })
  var node = svg.selectAll(".node").data(data);
  node.exit().remove();
  node.enter()
    .append("g")
    .classed("node", true)
    .append("circle")
    .attr("fill", function(d) {
      return color(d.size)
    })
    .attr("r", function(d) {
      return d.size;
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })

  node.select("circle")
    .transition()
    .duration(1000)
    .attr("fill", function(d) {
      return color(d.size)
    })
    .attr("r", function(d) {
      return d.size;
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Update</button>
<br>
<svg></svg>