Reputation: 1198
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
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
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