【问题标题】:Adding link to d3js force layout cause bad data in nodes添加到 d3js 强制布局的链接会导致节点中的数据错误
【发布时间】:2015-06-23 21:48:00
【问题描述】:

我正在做一个 d3js 副项目,我正在使用强制布局来显示节点和线来表示图形。我已经布置了代码,以便可以动态更新图形。我这样做是:

  1. 清除 force.nodes() 然后 force.links()
  2. 将我要添加的所有节点推送到 force.nodes()
  3. 更新图表(加入、进入、更新、退出)
  4. 将我想要的所有链接与 force.nodes() 中节点的引用一起推送到 force.links
  5. 更新图表(加入、进入、更新、退出)

只要我只有要显示的节点,它就可以工作,但是一旦我尝试将链接推送到 force.links,那么所有的地狱都会崩溃,两个节点最终会隐藏在左上角。

查看 DOM 我可以看到以下内容:

如您所见,transform/translate 参数包含 NaN 值。所以有些东西在我的脸上爆炸了,但经过两天的错误搜寻后,我怀疑我在这里遗漏了一些东西,需要洗个冷水澡。

我已将我的代码精简到可以重现错误的最小集合。请注意,节点显示正常,直到链接被推入 force.links。没有绘制链接,只有节点,但是将链接推送到它所属的位置的行为会破坏节点中的数据。按照我看到的示例,这种更新图表的方式应该可以工作。

d3.json("data/fm.json", function(error, graph) {
    if (error) throw error;
    function chart(elementName) {

        // look for the node in the d3 layout
        var findNode = function(name) {
            for (var i in nodes) {
                if (nodes[i]["name"] === name) return nodes[i];
            };
        };           

        var width = 960, // default width
            height = 450, // default height
            color = d3.scale.category10(),
            force = d3.layout.force(),
            nodes = force.nodes(),
            links = force.links(),
            vis,
            runOnceFlag = true;

        vis = d3.select(elementName)
                .append("svg:svg")
                .attr("width", width)
                .attr("height", height)
                .attr("id", "svg")
                .attr("pointer-events", "all")
                .attr("viewBox", "0 0 " + width + " " + height)
                .attr("perserveAspectRatio", "xMinYMid")
                .append('svg:g');            

        var update = function() {               

            var node = vis.selectAll("g.node")
                    .data(nodes, function (d) {
                        return d.name;
                    });

            var nodeEnter = node.enter().append("g")
                    .attr("class", "node")
                    .call(force.drag);

            nodeEnter.append("svg:circle")
                    .attr("r", 12)
                    .attr("id", function (d) {
                        return "Node;" + d.name;
                    })
                    .attr("class", "nodeStrokeClass")
                    .attr("fill", function(d) { return color(d.group); });

            nodeEnter.append("svg:text")
                    .attr("class", "textClass")
                    .attr("x", 14)
                    .attr("y", ".31em")
                    .text(function (d) {
                        return d.name;
                    });

            node.exit().remove();

            force.on("tick", function () {

                node.attr("transform", function (d) {
                    console.log(d);
                    return "translate(" + d.x + "," + d.y + ")";
                }); 

            });

            // Restart the force layout.
            force
                .charge(-120)
                .linkDistance( function(d) { return d.value * 10 } )
                .size([width, height])
                .start();
        };

        var a = graph.nodes[0];
        var b = graph.nodes[1]
        nodes.push(a);
        update();
        nodes.push(b);
        update();
        var c = {"source": findNode('a'), "target": findNode('b')}
        // the line below causes the error
        links.push(c);
        update()           
    };
    ///
    chart('body');
});

这是我的数据:

{
    "nodes":[
      {"name":"a", "group":1},
      {"name":"b", "group":2},
      {"name":"c", "group":3},
      {"name":"d", "group":4},
      {"name":"e", "group":5},
      {"name":"f", "group":6},
      {"name":"g", "group":7},
      {"name":"h", "group":1},
      {"name":"i", "group":2},
      {"name":"j", "group":3},
      {"name":"k", "group":4},
      {"name":"l", "group":5},
      {"name":"m", "group":6},
      {"name":"n", "group":7}
  ],
    "links":[
      {"source":0,"target":1,"value":1},
      {"source":2,"target":3,"value":1},
      {"source":4,"target":5,"value":1},
      {"source":7,"target":8,"value":1},
      {"source":9,"target":10,"value":1},
      {"source":11,"target":12,"value":1},
      {"source":0,"target":5,"value":1},
      {"source":1,"target":5,"value":1},
      {"source":0,"target":6,"value":1},
      {"source":1,"target":6,"value":1},
      {"source":0,"target":7,"value":1},
      {"source":1,"target":7,"value":1},
      {"source":2,"target":8,"value":1},
      {"source":3,"target":8,"value":1},
      {"source":2,"target":9,"value":1},
      {"source":3,"target":9,"value":1},
      {"source":4,"target":11,"value":1},
      {"source":5,"target":11,"value":1},
      {"source":9,"target":12,"value":1},
      {"source":10,"target":12,"value":1},
      {"source":11,"target":13,"value":1},
      {"source":12,"target":13,"value":1}
    ]
  }

【问题讨论】:

  • 你的数据是什么样的?
  • @StephenThomas 数据已添加到帖子中
  • 如果用var c = {"source": 0, "target": 1}替换麻烦的语句会发生什么?我不确定 D3 是否可以处理将索引与对象引用混合的初始链接数组。
  • 它应该能够按照文档使用引用。索引只是为了方便加载 JSON 数据。模拟开始后,d3 将这些索引替换为对它们所代表的对象的引用。我也知道它应该可以工作,因为我的代码基于这个块:bl.ocks.org/ericcoopey/6c602d7cb14b25c179a4
  • 添加链接后需要运行force.start()。每次更改节点或链接的结构时,都必须重新开始布局。这将根据新数据初始化一些数组。如果您不这样做,force.tick() 方法将在其计算中遇到一些未定义的值,这就是 NaN 的来源。

标签: javascript d3.js


【解决方案1】:

您的代码存在一些基本问题,请参阅下面的corrected concept...

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
  <style>
    .link {
      stroke: #2E2E2E;
      stroke-width: 2px;
    }

    .node {
      stroke: #fff;
      stroke-width: 2px;
    }
    .textClass {
      stroke: #323232;
      font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
      font-weight: normal;
      stroke-width: .5;
      font-size: 14px;
    }
  </style>

</head>
<body>
<!--<script src="d3 CB.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
  d3.json("data.json", function (error, graph) {
    if (error) throw error;
    function chart(elementName) {

      // look for the node in the d3 layout
      var findNode = function (name) {
        for (var i in nodes) {
          if (nodes[i]["name"] === name) return nodes[i];
        }
      };

      var width = 960, // default width
        height = 450, // default height
        color = d3.scale.category10(),
        nodes = graph.nodes,
        links = graph.links,
        force = d3.layout.force()
          .nodes(nodes)
          .links([]),
        vis,
        runOnceFlag = true;

      vis = d3.select(elementName)
        .append("svg:svg")
        .attr("width", width)
        .attr("height", height)
        .attr("id", "svg")
        .attr("pointer-events", "all")
        .attr("viewBox", "0 0 " + width + " " + height)
        .attr("perserveAspectRatio", "xMinYMid")
        .append('svg:g');

      var update = function () {
        var link = vis.selectAll("line")
          .data(force.links(), function (d) {
            return d.source + "-" + d.target;
          });

        link.enter().insert("line", "g")
          .attr("id", function (d) {
            return d.source + "-" + d.target;
          })
          .attr("stroke-width", function (d) {
            return d.value / 10;
          })
          .attr("class", "link")
          .style("stroke", "red")
          .transition().duration(5000).style("stroke", "black");
        link.append("title")
          .text(function (d) {
            return d.value;
          });
        link.exit().remove();

        var node = vis.selectAll("g.node")
          .data(nodes, function (d) {
            return d.name;
          });

        var nodeEnter = node.enter().append("g")
          .attr("class", "node")
          .call(force.drag);

        nodeEnter.append("svg:circle")
          .attr("r", 12)
          .attr("id", function (d) {
            return "Node;" + d.name;
          })
          .attr("class", "nodeStrokeClass")
          .attr("fill", function (d) {
            return color(d.group);
          });

        nodeEnter.append("svg:text")
          .attr("class", "textClass")
          .attr("x", 14)
          .attr("y", ".31em")
          .text(function (d) {
            return d.name;
          });

        node.exit().remove();

        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) {
            console.log(d);
            return "translate(" + d.x + "," + d.y + ")";
          });

        });

        // Restart the force layout.
        force
          .charge(-120)
          .linkDistance(function (d) {
            return d.value * 100
          })
          .size([width, height])
          .start();
      };

      update();
      var c = {"source": findNode('a'), "target": findNode('b'), value: 1}
      // the line below causes the error
      window.setTimeout(function() {
        force.links().push(c);
        update()
      },2000)
    };
    //
    chart('body');
  });
</script>
</body>
</html>

【讨论】:

  • 我对您的代码与我的代码进行了逐行比较,除了有关订购 DOM 的问题外,该错误是由于缺少链接的“值”属性引起的。谢谢
  • d3 的水平和有些随意的抽象可以产生有趣的心理过程。对不起关于force.start()的鹅追逐;我已经多次看到这种症状,而且总是同样的问题。是的,我认为您链接中缺少的成员是问题所在。我还认为我对selection.insert 的使用(我敢说吗?)比原来的方法更优雅一点。 :p
  • 是的,完全同意,您对 insert() 的使用非常酷,给您高五 :) 如果您有兴趣,我会在完成后向您更新我的副项目。
  • 谢谢,是的,我肯定会感兴趣的。
  • 这里是侧项目代码。它已经完成了 90%。这是一个可重复使用的图表,具有 MVC 布局github.com/TimeBandit/graphSub
猜你喜欢
  • 2015-12-04
  • 2012-03-21
  • 1970-01-01
  • 2018-10-10
  • 2014-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多