Search code examples
javascriptd3.jsd3-force-directed

D3 force layout data doesn't update


I'm using D3's forced layout graph to plot the data.

  1. When I call the update function using setInterval with new data, the force layout graph nodes start from a random position. How can I fix this?
  2. The data is not getting updated even though console prints the correct data. How can I solve this too?

Here is the jsfiddle: https://jsfiddle.net/mootqvs1/


Solution

  • (Please avoid asking about different issues in the same question, specially when they are not related. Here I'll explain your problem #2, which is specific to your code. Your problem #1 is a general problem and has been already addressed in several questions/answers here at S.O.)

    The problem with the update function is that you're using the indices as keys. However, the indices are the same for those different datasets!

    Therefore, change this...

    if (!node.id) node.id = ++i;
    

    ... for something that assigns unique ids for the nodes. The obvious choice is using their names:

    if (!node.id) node.id = node.name;
    

    However, even that won't work, because some nodes have the same name... so, we can mix the approaches:

    if (!node.id) node.id = node.name + (++i);
    

    Have in mind that this is just an example. The best solution is finding some property (or combination of properties) which are always unique and the same for each node.

    Here is your code with that change only:

    var width = 680,
      height = 380,
      root;
    
    var data1 = {
      "name": "RootNode",
      "children": [{
        "name": "B1",
        "children": [{
          "name": "D1"
        }, {
          "name": "D2"
        }, {
          "name": "D3"
        }]
      }, {
        "name": "B2",
        "children": [{
          "name": "D1"
        }, {
          "name": "D2"
        }, {
          "name": "D3"
        }, {
          "name": "D4"
        }]
      }, {
        "name": "B3",
        "children": [{
          "name": "D1"
        }, {
          "name": "D2"
        }, {
          "name": "D3"
        }, {
          "name": "D4"
        }, {
          "name": "D5"
        }]
      }, {
        "name": "B4",
        "children": [{
          "name": "D1"
        }, {
          "name": "D2"
        }, {
          "name": "D3"
        }, {
          "name": "D4"
        }]
      }, {
        "name": "B5",
        "children": [{
          "name": "D1"
        }]
      }]
    };
    
    var data2 = {
      "name": "Root",
      "children": [{
        "name": "Box1",
        "children": [{
          "name": "device1"
        }, {
          "name": "device2"
        }, {
          "name": "device3"
        }]
      }, {
        "name": "Box2",
        "children": [{
          "name": "device1"
        }, {
          "name": "device2"
        }, {
          "name": "device3"
        }, {
          "name": "device4"
        }]
      }, {
        "name": "Box3",
        "children": [{
          "name": "device1"
        }, {
          "name": "device2"
        }, {
          "name": "device3"
        }, {
          "name": "device4"
        }, {
          "name": "device5"
        }]
      }, {
        "name": "Box4",
        "children": [{
          "name": "device1"
        }, {
          "name": "device2"
        }, {
          "name": "device3"
        }, {
          "name": "device4"
        }]
      }, {
        "name": "Box5",
        "children": [{
          "name": "device1"
        }]
      }]
    };
    
    var force = d3.layout.force()
      .linkDistance(80)
      .charge(-120)
      .gravity(.05)
      .size([width, height])
      .on("tick", tick);
    
    var svg = d3.select(".network-graph").append("svg")
      .attr("width", width)
      .attr("height", height);
    
    var link = svg.selectAll(".link"),
      node = svg.selectAll(".node");
    
    root = data1;
    update();
    
    
    setInterval(function() {
      root = data2;
      update();
    }, 5000);
    
    
    function update() {
      var nodes = flatten(root),
        links = d3.layout.tree().links(nodes);
    
    
      // Restart the force layout.
      force
        .nodes(nodes)
        .links(links)
        .start();
    
      // Update links.
      link = link.data(links, function(d) {
        return d.target.id;
      });
    
      link.exit().remove();
    
      link.enter().insert("line", ".node")
        .attr("class", "link");
    
      // Update nodes.
      node = node.data(nodes, function(d) {
        return d.id;
      });
    
      node.exit().remove();
    
      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .on("click", click)
        .call(force.drag);
    
      nodeEnter.append("circle")
        .attr("r", function(d) {
          return 15 || 4.5;
        });
    
      nodeEnter.append("text")
        .attr("dy", ".35em")
        .text(function(d) {
          return d.name;
        });
    
      node.select("circle")
        .style("fill", color);
    
    }
    
    function tick() {
      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) {
        return "translate(" + d.x + "," + d.y + ")";
      });
    }
    
    function color(d) {
      return d._children ? "#3182bd" // collapsed package
        : d.children ? "#c6dbef" // expanded package
        : "#fd8d3c"; // leaf node
    }
    
    // Toggle children on click.
    function click(d) {
      if (d3.event.defaultPrevented) return; // ignore drag
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }
      update();
    }
    
    // Returns a list of all nodes under the root.
    function flatten(root) {
      var nodes = [],
        i = 0;
    
      function recurse(node) {
        if (node.children) node.children.forEach(recurse);
        if (!node.id) node.id = node.name + (++i);
        nodes.push(node);
      }
    
      recurse(root);
      return nodes;
    }
    .node circle {
      cursor: pointer;
      stroke: #3182bd;
      stroke-width: 1.5px;
    }
    
    .node text {
      font: 10px sans-serif;
      pointer-events: none;
      text-anchor: middle;
    }
    
    line.link {
      fill: none;
      stroke: #9ecae1;
      stroke-width: 1.5px;
    }
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <div class="network-graph"></div>