Search code examples
javascriptd3.jsd3-force-directed

Position of node changes when they link (using d3.js)


I am a newbie in d3.js. I have tried to create a static architecture with 5 nodes and link them with each other according to preferences, the nodes should be organized like so:

enter image description here

At the beginning I set the position of the nodes and then create the links. Though, when the nodes get linked, the architecture changes and the result is the one displayed below:

enter image description here

Here is my code:

    var width = 640,
        height = 400;


    var nodes = [
              { x:   60, y: 0, id: 0},
              { x:   150, y: height/4, id: 1},
              { x:   220, y: height/4, id: 2},
              { x:   340, y: height/4, id: 3},
              { x:   420, y: height/2, id: 4},
              { x:   480, y: height/2, id: 5}
    ];

    var links = [
              { source: 1, target: 5 },
              { source: 0, target: 5 },
              { source: 2, target: 1 },
              { source: 3, target: 2 },
              { source: 4, target: 5 }
    ];

    var graph = d3.select('#graph');

    var svg = graph.append('svg')
                   .attr('width', width)
                   .attr('height', height);

    var force = d3.layout.force()
                        .size([width, height])
                        .nodes(nodes)
                        .links(links);

    force.linkDistance(width/2);

    var link = svg.selectAll('.link')
                  .data(links)
                  .enter().append('line')
                  .attr('class', 'link');

     var div = d3.select("body").append("div")
                 .attr("class", "tooltip")
                 .style("opacity", 1e-6);

      var node = svg.selectAll('.node')
              .data(nodes)
              .enter().append("circle")
              .attr("cx", d=> d.x)
              .attr("cy", d=> d.y)
              .attr('class', 'node')
              .on("mouseover", function(d){ 
                              d3.select(this)
                                  .transition()
                                  .duration(500)
                                  .style("cursor", "pointer")
                                  div
                                    .transition()  
                                    .duration(300)
                                    .style("opacity", "1")                           
                                    .style("display", "block")  
                                    console.log("label", d.label);
                                  div
                                    .html("IP: " +  d.label + " x: " + d.x + " y: " + d.y)
                                    .style("left", (d3.event.pageX ) + "px")
                                    .style("top", (d3.event.pageY) + "px");

                                })
              .on("mouseout", mouseout);


       function mouseout() {
          div.transition()
             .duration(300)
             .style("opacity", "0")
        }

       console.log("wait...");
       force.on('end', function() {

             node.attr('r', width/25)
                 .attr('cx', function(d) { return d.x; })
                 .attr('cy', function(d) { return d.y; });

             link.attr('x1', function(d) { console.log("LINE x1-> ", d.source.x); return d.source.x; })
                 .attr('y1', function(d) { console.log("LINE y1-> ", d.source.y); return d.source.y; })
                 .attr('x2', function(d) { console.log("LINE x2-> ", d.source.x); return d.target.x; })
                 .attr('y2', function(d) { console.log("LINE y2-> ", d.source.y); return d.target.y; })
                 .attr("stroke-width", 2)
                 .attr("stroke","black");
       });

       force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph"></div>

Could you please help me? Thank you in advance.


Solution

  • A force layout offers some advantages that derive from its nature as a self organizing layout:

    • It places nodes and links automatically avoiding manual positioning of potentially thousands of elements
    • It organizes nodes and links based on assigned forces to an ideal spacing and layout

    You have nodes to which you have already assigned positions, the two advantages listed above do not apply. You've already manually done the first item, and the second item will disturb and overwrite the positions you manually set.

    We could fix the node positions, but if we do this with all nodes, it defeats the purpose of the force layout: to position nodes by simulating forces.

    Instead, if you have the position of all nodes, we can skip the force and just append everything based on the data. The snippet below places the links first (so they are behind the nodes) using the index contained in d.source/d.target to access the specific node in the nodes array and get the appropriate x or y coordinate. The nodes are positioned normally.

    It appears you have adjusted the code to use circles in your question though the screenshot uses images (as you also used in a previous question), I'll just use circles here. Based on the coordinates you've given some lines overlap. I modified the first node so that the y value wasn't 0 (which would have pushed half the circle off the svg)

    var width = 640,
        height = 400;
        
    var nodes = [
              { x:   60, y: height/8, id: 0},
              { x:   150, y: height/4, id: 1},
              { x:   220, y: height/4, id: 2},
              { x:   340, y: height/4, id: 3},
              { x:   420, y: height/2, id: 4},
              { x:   480, y: height/2, id: 5}
    ];
    
    var links = [
              { source: 1, target: 5 },
              { source: 0, target: 5 },
              { source: 2, target: 1 },
              { source: 3, target: 2 },
              { source: 4, target: 5 }
    ];
    
    var graph = d3.select('#graph');
    
    var svg = graph.append('svg')
                   .attr('width', width)
                   .attr('height', height);
           
    // append links:
    svg.selectAll()
      .data(links)
      .enter()
      .append("line")
      .attr("x1", function(d) { return nodes[d.source].x; })
      .attr("y1", function(d) { return nodes[d.source].y; })
      .attr("x2", function(d) { return nodes[d.target].x; })
      .attr("y2", function(d) { return nodes[d.target].y; })
      .attr("stroke-width", 2)
      .attr("stroke","black");
      
    // append nodes:
    svg.selectAll()
      .data(nodes)
      .enter()
      .append("circle")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", 8);
      
      
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <div id="graph"></div>