d3.js Molecule Diagram only working on the last element of the object

Viewed 60

so I'm trying to create a visual representations of a couple of vlans and the connections of switches in each of them. I tried implementing it with this example I found online https://bl.ocks.org/mbostock/3037015 , the problem is that when i created a loop to go through all of the vlans, only the last vlan is drawn, there's really no reason I can see of why this is happening since all elements are calling the function.

If I remove the last element from the array with delete data['80'] then the one before the last starts working, so the only one working it the last one of the dictionary object, don't why though

code:

 var data = {{ graph_vlans | safe }};
 console.log(data);
    $(document).ready(() => {
        //-----------------------------------------------------------------
        // TREE DISPLAY ---------------------------------------------------
        //-----------------------------------------------------------------

        var toggler = document.getElementsByClassName("caret");
        for (var i = 0; i < toggler.length; i++) {
            toggler[i].addEventListener("click", function () {
                this.parentElement.querySelector(".nested").classList.toggle("active");
                this.classList.toggle("caret-down");
            });
        }


        //-----------------------------------------------------------------
        // NETWORK DIAGRAM ------------------------------------------------
        //-----------------------------------------------------------------
        var width = 960, height = 500;
        var color = d3.scale.category20();
        var radius = d3.scale.sqrt().range([0, 6]);

        var i = 0;

        for (var key in data) {

            console.log(key);
            console.log(key["4"]);

            var svg = d3.select("#graph_" + key).append("svg").attr("width", width).attr("height", height);

            var force = d3.layout.force()
                .size([width, height])
                .charge(-400)
                .linkDistance(function (d) {
                    return radius(d.source.size) + radius(d.target.size) + 20;
                });

            var graph = data[key];

            var link = svg.selectAll(".link")
                .data(graph.links)
                .enter().append("g")
                .attr("class", "link");

            link.append("line")
                .style("stroke-width", function (d) {
                    return (d.bond * 2 - 1) * 2 + "px";
                });

            link.filter(function (d) {
                return d.bond > 1;
            }).append("line")
                .attr("class", "separator");

            var node = svg.selectAll(".node")
                .data(graph.nodes)
                .enter().append("g")
                .attr("class", "node")
                .call(force.drag);

            node.append("circle")
                .attr("r", function (d) {
                    return radius(d.size);
                })
                .style("fill", function (d) {
                    return color(d.atom);
                });

            node.append("text")
                .attr("dy", ".35em")
                .attr("text-anchor", "middle")
                .text(function (d) {
                    return d.atom;
                });


            force.nodes(graph.nodes)
                .links(graph.links)
                .on("tick", tick)
                .start();

            i++;
        }

        function tick() {
            link.selectAll("line")
                .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 + ")";
            });
        }
    });
1 Answers

Problem

I made some fake data for your plot and got this:

enter image description here

Your other force layouts are drawing, they're just not positioned. They're at [0,0] - barely visible here, in the top left corner of the SVG. So why is this?

Each for loop iteration you redefine any existing link and node variables - their scope extends beyond the for statement so you overwrite the previous defintion. var restricts a variables scope by function, the for statement doesn't limit scope if using var.

Because of this, when you call the tick function for each force layout, only the last layout is updated because node and link refer to the last layouts nodes and links.

So only your last force layout does anything.

Solution

There are a few solutions, I'm proposing one that adds two simple changes from your current code.

We need to get each force layout's nodes and links to the tick function. Currently we have all the force layout tick functions using the same node and link references. Ultimately, this is a variable scoping issue.

We can start by placing the tick function into the for loop. But, this still runs into the same problem by itself: node and link have a scope that isn't limited to the for loop (or the current iteration of the for loop) - each tick function will still use the same node and link references.

To fix this, we also need to use let when defining link and node (instead of var), now these variables have a block level scope, meaning each iteration's definitions of link and node won't overwrite the previous iterations.

By moving the tick function into the for loop and using let to define node and link, each time we call the tick function it will use the appropriate nodes and links.

Here's an example using a slightly modified example of the above code (removing some of the styling that relies on data properties and re-sizing the layouts for snippet view, but with the changes proposed above):

var data = {
  "a":{
    nodes:[{name:1},{name:2},{name:3}],
    links:[
      {source:1, target:2},
      {source:2, target:0},
      {source:0, target:1}
      ]
  },
  "b":{
    nodes:[{name:"a"},{name:"b"},{name:"c"}],
    links:[
      {source:1, target:2},
      {source:2, target:0},
      {source:0, target:1}
      ]
  }
}
        // TREE DISPLAY 
        var width = 500, height = 100;
        var color = d3.scale.category20();
        var radius = d3.scale.sqrt().range([0, 6]);

        var i = 0;

        for (var key in data) {

            var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);

            var force = d3.layout.force()
                .size([width, height])
                .charge(-400)
                .linkDistance(20);

            var graph = data[key];

            let link = svg.selectAll(".link")
                .data(graph.links)
                .enter().append("g")
                .attr("class", "link");

            link.append("line")
                .style("stroke-width", 1)
                .style("stroke","#ccc")

            let node = svg.selectAll(".node")
                .data(graph.nodes)
                .enter().append("g")
                .attr("class", "node");

            node.append("circle")
                .attr("r", 5)
                .attr("fill","#eee");

            node.append("text")
                .attr("dy", ".35em")
                .attr("text-anchor", "middle")
                .text(function (d) {
                    return d.name;
                });


            force.nodes(graph.nodes)
                .links(graph.links)
                .on("tick", tick)
                .start();

            i++;
            
        function tick() {
            link.selectAll("line")
                .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 + ")";
            });
        }            
            
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

Related