chemook78
chemook78

Reputation: 1198

How to position nodes Force Directed Graph D3 with CSS?

I am making a Force Directed Graph with D3 and got it working with svg circles for the nodes. But when I use divs in combination with CSS left and top properties, the nodes are out of position. I can't figure out what I am doing wrong here. I use the x and y coordinates that force D3 generates as the left and top properties, but maybe that's not the way to go?

Here is my JS:

var url = "https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json";

d3.json(url, function(json){

  var data = json;

  var margin = {top: 40, right: 40, bottom: 40, left: 40};

  var w = 1000 - margin.left - margin.right;
  var h = 1000 - margin.top - margin.bottom;

  var svg = d3.select("#chart")
              .append("svg")
                .attr("width", w + margin.left + margin.right)
                .attr("height", h + margin.top + margin.bottom)
              .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var nodes = data.nodes;
  var links = data.links;

  //Create a force layout object and define its properties
  var force = d3.layout.force()
                .size([w,h])
                .nodes(nodes)
                .links(links);

  force.linkDistance(h/20);

  force.charge(-120)

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

  var node = d3.select("#chart").selectAll(".node")
                .data(nodes)
                .enter()
                .append("div")

  force.on("end", function(){
  //set node and link position attributes once force calculations have finished  

    //position the nodes
    node.style("left", function(d){

            return d.x  + "px";

        })
        .style("top", function(d){

            return d.y + "px";

        })
        .attr("class", function(d){

            return "flag flag-" + d.code + " node";

        })
        .attr("src", "https://res.cloudinary.com/dettjqo9j/image/upload/v1485942660/flags_xf9dde.png");

    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;

        })



  })//force.on


  force.start();
  //let the force start its calculations

})

My css:

svg {

  background-color: white;
  box-shadow: 0 0 10px #888888;

}

.link {

  stroke: #2c3e50;
  stroke-width: 2px;

}

.flag {
    display: inline-block;
    position:absolute;
    width: 16px;
    height: 11px;
    background: url('https://res.cloudinary.com/dettjqo9j/image/upload/v1485942660/flags_xf9dde.png') no-repeat;
}

.flag.flag-ml {
    background-position: -224px -88px;
}

Graph so far on codepen:

https://codepen.io/chemok78/full/VPMgGx/

Upvotes: 0

Views: 1139

Answers (2)

chemook78
chemook78

Reputation: 1198

Thanks all,

I ended up getting the position of the SVG g element of the chart using Element.getBoundingClientRect() and using the left top position of that in positioning all the nodes.

//Get position of g element of the chart
  var position = document.getElementById("area").getBoundingClientRect();

force.on("tick", function(){

    node.style("left", function(d){

            return d.x  + position.left + "px";

        })
        .style("top", function(d){

            return d.y  + position.top  +  "px";

        })
        .attr("class", function(d){

            return "flag flag-" + d.code + " node";

        })
        .attr("src", "https://res.cloudinary.com/dettjqo9j/image/upload/v1485942660/flags_xf9dde.png")

    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;

        })

  })

Upvotes: 0

anbnyc
anbnyc

Reputation: 921

Account for how much the links in the SVG are offset from the window: they're shifted over by margin.left, down by margin.top, and also down by the heights of the <h1> and <h2> (plus padding).

For example, in my browser, the flags look positioned correctly with the following code:

node.style("left", function(d){

        return d.x + margin.left + 150 + "px";

    })
    .style("top", function(d){

        return d.y + margin.top + "px";

    })

Notice that this is because the <div> flag elements have a default left and top attribute of 0, even though their parent is <div#chart>. I've hard-coded 150 but you could also devise a way to calculate it dynamically.

The reason you got it working with <circle> but not <div> is that circles are SVG elements and children of <svg>, so they are already in the correct starting positions. There might be an alternative solution in which you keep the flags within SVGs: adding nodes as <g> or <rect> instead of <div>, attributes of x and y instead of left and top, children of <svg> instead of <div#chart>. However, I haven't explored how to implement assigning the flags to each node because you're not pointing at a distinct URL for each flag file.

Upvotes: 2

Related Questions