【问题标题】:d3 enter() / merge() creates copies instead of updatingd3 enter() / merge() 创建副本而不是更新
【发布时间】:2017-07-18 12:41:08
【问题描述】:

我正在使用 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 中画出几十个圆圈,一个在另一个之上(当它们是半透明时很容易看到)。

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

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    浏览您的代码,我们可以看到您对 有“更新”/“进入”/“退出”选择,但对于 圈子 没有选择.

    因此,当一个组既不在“进入”选项中,也不在“退出”选项中,意味着它在“更新”选项中,您将在其上附加一个圆圈每次运行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>

    【讨论】:

    • 这似乎对我不起作用,可能是因为在您的解决方案中您没有重新分配 node 变量。 (?)因为我使用力模拟,我也在一个单独的tick() 方法中使用node,比如:node.attr("transform", function(d) {...}),当我尝试按照您的建议进行操作时,节点的位置根本不会更新(它们'都在左上角)。
    • 如果没有minimal reproducible example,真的很难提供答案。但是,请记住,我的回答确实解决了您的问题标题中所述的问题,并说明了如何解决它。如果您还有其他问题,我建议您发布另一个问题,或编辑此问题以明确说明您的问题并创建minimal reproducible example
    • 好的,没关系,刚刚解决了这个问题。谢谢,你的回答很有用!
    猜你喜欢
    • 2018-06-08
    • 2015-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多