timebandit
timebandit

Reputation: 830

Adding link to d3js force layout cause bad data in nodes

I am working on a d3js side project where I am using the force layout to show nodes and lines to represent a graph. I have laid out the code such that the graph can be dynamically updated. I am doing this by:

  1. Clearing force.nodes() then force.links()
  2. Pushing all the nodes I want to add to force.nodes()
  3. Updating the graph (join, enter, update, exit)
  4. Pushing all the links I want with references to the nodes in force.nodes() to force.links
  5. Updating the graph (join, enter, update, exit)

This works as long as I only have nodes to display, but as soon as I attempt to push a link to force.links then all hell breaks loose and the two nodes end up hiding in the top left corner.

Looking at the DOM I can see the following:

DOM

As you can see, the transform/translate parameters contain NaN values. So something blew up in my face but after two days of bug hunting I suspect that I am missing something here and need to take a cold shower.

I have stripped down my code to the smallest set that can reproduce the error. Please note from it that the nodes display fine until a link is pushed into force.links. No links are being drawn, only nodes, but the act of pushing a link where it belongs is disrupting the data in the nodes. This way of updating a graph should work as per the examples I have seen.

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');
});

This is my data:

{
    "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}
    ]
  }

Upvotes: 0

Views: 1121

Answers (1)

Cool Blue
Cool Blue

Reputation: 6476

You have some basic problems with your code, see corrected concept below...

<!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>

Upvotes: 2

Related Questions