1

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.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
T. ksaps
  • 87
  • 8

1 Answers1

1

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>
Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Andrew thank you for your help. I have changed circles with images in your code and the result is not the same. Do I have to change something particular? – T. ksaps Jul 07 '18 at 20:45
  • Other than appending an `image`, and positioning with `x`/`y` rather than `cx`/`cy` (and assigning a URI for the image file) it should be the same. – Andrew Reid Jul 07 '18 at 20:47
  • @T.ksaps, also circles are positioned by their center, images by their top left corner - so you need to modify the x/y positions by half the image height/width so that they are centered on the specified coordinate. Here's an example using code from your previous question in respect to images (but otherwise based on the snippet above): http://bl.ocks.org/Andrew-Reid/bc0019871e26f3da22f8a4a0cddcc4ca – Andrew Reid Jul 07 '18 at 20:59
  • Thank you @Andrew for the example and the very important help! – T. ksaps Jul 07 '18 at 21:10