I was able to replicate one of Mike Bostock's Force Dragging example from his blog
However I am not sure why when I try to simulate it in my own style, it crashes.
Here's a working code, mimicking exactly's Bostock's code, except using a different JSON data
Here's the link to JSON data
Codepen - Working
Here's my own attempt :
Codepen - Not Working
$( document ).ready(function(){
const w = 1000;
const h = 700;
const margin = {
top: 90,
bottom: 90,
left: 90,
right: 90
}
function title(){
}
function render(data){
const width = w - (margin.left + margin.right);
const height = h - (margin.up + margin.down);
let svg = d3.select("#canvas")
.append("svg")
.attr("id","chart")
.attr("width", w)
.attr("height", h)
let chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d,i) {
return i;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
let link = chart.append("g")
.classed("links",true)
.selectAll("line")
.data(data.links)
.enter()
.append("line")
let node = chart.append("g")
.classed("nodes",true)
.selectAll("circle")
.data(data.nodes)
.enter()
.append("circle")
.attr("r", 2.5)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
node.append("title")
.text(function(d) { return d.country; });
simulation
.nodes(data.nodes)
.on("tick", ticked);
simulation.force("link")
.links(data.links);
//functions provided by D3.js
//
function ticked() {
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;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
const url = 'https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json';
$.ajax({
type: "GET",
dataType: "json",
url: url,
beforeSend: ()=>{
},
complete: () =>{
},
success: data =>{
render(data)
},
fail: () =>{
console.log('failure!')
},
error: () =>{
}
});
});
Change the variable names up and down used for height calculation to top and bottom. Height attribute was set NaN due to this typo in margin variables.
Change this line-
const height = h - (margin.up + margin.down);
to
const height = h - (margin.top + margin.bottom);
Updated codepen: http://codepen.io/anon/pen/XjVyGx
Related
I am creating a force directed graph using v4 of d3.js. The links in the graph are always pointing to the left hand corner of the rectangular nodes.
I think the problem is either in my tick function or my drag handlers. I am not really sure which one is it and what is the problem.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation().nodes(nodes_data);
simulation
.force("centre_force", d3.forceCenter(width / 2, height / 2))
.force("charge_force", d3.forceManyBody().strength(-90))
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("rect")
.data(nodes_data)
.enter()
.append("g");
var rect_width = 80;
var rect_height = 50;
nodes.append("rect")
.attr("width", rect_width)
.attr("height", rect_height)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "black");
nodes.append("text")
.attr("text-anchor", "middle")
.attr("x", rect_width / 2)
.attr("y", rect_height / 2)
.attr("dy", "0.35em")
.text(function (d) { return d.name; });
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2);
//defining force for links
var link_force = d3.forceLink(links_data)
.id(function (d) { return d.name; })
.distance(150)
.strength(0.5);
//calling the defined force
simulation.force("links", link_force);
simulation.on("tick", function () {
nodes.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
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; });
nodes.attr("x", (d) => { return d.x; })
.attr("y", (d) => { return d.y; });
})
//drag function definitions
function drag_start(d) {
simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//drag function binding
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(nodes);
I want them to point at the edge of the rectangle and the edge should change dynamically if the position is changed so.
Following is the actual result:
Following is the expected result:
I solved the problem.
First of all, I removed the following code
nodes.attr("x", (d) => { return d.x; })
.attr("y", (d) => { return d.y; });
There was no need for it.
Next, I changed the x and y co-ordinates of the rect element to point to the center of the rect. The default values for x and y is 0 and 0.
In my case, I changed the x to -40 and to y to -25 as following
let rect_width = 80;
let rect_height = 50;
nodes.append("rect")
.attr("width", rect_width)
.attr("height", rect_height)
.attr("rx", 10)
.attr("ry", 10)
.attr("x", -(rect_width / 2))
.attr("y", -(rect_height / 2))
.style("stroke", "black");
Following is my result now
Possibly unecessary information- but to move the links behind my node, I drew the links first and then the nodes later in my code. Referred this answer
I created a D3 visualization, but after the complete load, my visualisation isn't center :
I would like to have this result :
I use Angular 5 to execute my D3 visualization. My Data is loaded, it's work, but my visualization is on the left and not center. I would like to center my visualization. My code from my component :
export class BrainPageHomeComponent implements OnInit, AfterViewInit, OnDestroy {
public name: string;
public svg;
public color;
public simulation;
public link;
public node;
public miserables;
public tooltip;
constructor(
private brainService: BrainService,
) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.loadBrain();
}
//Load Brain
public loadBrain() {
d3.select("svg").remove();
this.brainService.getBrain().subscribe(
data => {
this.miserables = data['data'];
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function () {
let g = d3.select('svg > g');
g.attr("transform", d3.event.transform);
}))
.append("g");
let width = +this.svg.attr("width");
let height = +this.svg.attr("height");
this.color = d3.scaleOrdinal(d3.schemeCategory20);
this.simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
this.render(this.miserables);
},
error => {
console.log(error);
});
}
ticked() {
this.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;
});
this.node
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
}
render(graph) {
let tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip-message")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
this.link = this.svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function (d) {
return Math.sqrt(d.value);
});
this.node = this.svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", (d) => {
return this.color(d.group);
})
//npm .on("click", function(d, i) { alert("Hello world"); })
.on("mouseover", function(node){
console.log(node);
tooltip.text(node.title);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function(){return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
.call(d3.drag()
.on("start", (d) => {
return this.dragstarted(d)
})
.on("drag", (d) => {
return this.dragged(d)
})
.on("end", (d) => {
return this.dragended(d)
}));
this.node.append("title")
.text(function (d) {
return d.id;
});
this.simulation
.nodes(graph.nodes)
.on("tick", (nodes) => {
return this.ticked()
})
.stop();
this.simulation.force("link")
.links(graph.links);
this.simulation.restart();
}
dragstarted(d) {
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x, d.fy = d.y;
}
dragged(d) {
d.fx = d3.event.x, d.fy = d3.event.y;
}
dragended(d) {
if (!d3.event.active) this.simulation.alphaTarget(0);
d.fx = null, d.fy = null;
}
ngOnDestroy() {
}
}
I tried many things :
Added viewBox attribute :
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function () {
let g = d3.select('svg > g');
g.attr("transform", d3.event.transform);
}))
.attr("viewBox", "0 0 " + "100%" + " " + "100%" )
.attr("preserveAspectRatio", "xMidYMid meet");
.append("g");
Added restart simulation :
this.simulation.restart();
But my visualization is always on the left and not center.
I fixed my problem :
I replaced :
let width = +this.svg.attr("width");
let height = +this.svg.attr("height");
by :
let width = d3.select('svg').style("width");
let height = d3.select('svg').style("height");
width = width.substring(0, width.length - 2);
height = height.substring(0, height.length - 2);
I have implemented a force directed graph which visualizes shared borders between countries.The layout of graph goes out of svg boundaries,is there way to resize the graph size,so that graph layout stays within graph boundaries.can size of simulation be pre set to adjust to the change in the width and height of window?
link to codepen
let request = new XMLHttpRequest();
request.addEventListener("load", loaded);
function loaded() {
const data = JSON.parse(request.responseText);
var nodes = data.nodes;
var links = data.links;
// sets up svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
// handle color of the nodes i gueess,gotta know what schemeCategory method does
var color = d3.scaleOrdinal(d3.schemeCategory20);
// starts simulation
var simulation = d3
.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width /2, height / 2))
// creates lines in graph,
var link = svg
.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.sqrt(3);
});
//creates nodes..for this example,you need to set node to images
var node = svg
.append("g")
.attr("class", "nodes")
.selectAll(".node")
//pass node data
.data(nodes)
.enter()
.append("image")
.attr("xlink:href",function(d){return "https://cdn.rawgit.com/hjnilsson/country-flags/master/svg/"+d.code+".svg" })
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16)
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
node.append("title")
.text(function(d) { return d.country; });
simulation.nodes(nodes).on("tick", ticked);
simulation.force("link").links(links);
function ticked() {
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;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
request.open(
"GET",
"https://www.cs.mun.ca/~h65ped/Public/country%20data%20for%20force%20directed%20graph/countries.json",
true
);
request.send(null);
The attractive forces of the built-in d3.forceManyBody can be modified with the strength() method so try something like
// starts simulation
var simulation = d3
.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-5))
.force("center", d3.forceCenter(width /2, height / 2))
If that constrains the other items too closely then you would have to implement your own force method which can constrain items based on the SVG size. See d3.force for a description of what is happening under-the-hood and to see how you could produce your own function.
I'm new to d3.js and I'm currently stuck on a problem. I'm using the force directed graph to show relationships of my data. This should allow a user to add a new node to the existing graph and draw a relationship link between 2 or more nodes. My caveat is my data is being populated from an ajax call which I assign to a variable and pass that to a function which generates the graph. The initial load of the data works great and everything is displayed properly. My issue is when the user clicks a button to add the new node. On that action I am making a ajax call to retrieve a new un-linked relationship to add to the graph. I add the new retrieved data to the nodes array and attempt to re-draw the entire graph. However I receive errors on x & y attributes being set to NaN. I believe this is related to how forceSimulation assigns those values. I did attempt to use simulation.reset(), but it was not successful.
Here is some of my code;
Initial call to retrieve all existing relationships.
function getGraphData(){
$.ajax({
url: [link to rest uri],
type: 'GET',
contentType: 'application/json'
}).done(function(response){
drawGraph(response);
})
};
This is my second call to retrieve a new un-linked relationship
function getNewRelationshipData(){
$.ajax({
url: [link to second rest uri],
type: 'GET'
contentType: 'application/json'
}).done(function(response){
var newNode = response.nodes;
updateGraph();
//---same as getGraphData()
$.ajax({
url: [link to rest uri],
type: 'GET',
contentType: 'application/json'
}).done(function(response){
var graphData = response;
graphData.nodes[graphData.nodes.length] = newNode[0]
//assigned relationship data to graphData and appended the newNode value
drawGraph(graphData);
})
});
};
function updateGraph(){
// clears out old graph
d3.selectAll("svg > *").remove();
};
This is how I'm setting up my graph.
function drawGraph(relationships){
var svg = d3.select("svg"),
w = +svg.attr("width"),
h = +svg.attr("height);
var g = svg.append("g");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(60))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(w / 2, h / 2))
.force("attraceForce",d3.forceManyBody().strength(-900));
var opacity = 0.05;
var transitionPeriod = 500;
var graph = relationships;
var link = g.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("stroke-width", function(d) { return d.value; })
.style("stroke", "#999" )
.style("opacity", "1")
.attr("group",function(d) {return d.group; })
.on("click", function(d) {
// This is to toggle visibility - need to do it on the nodes, links & text
d3.selectAll("line:not([group='"+d.group+"'])")
.transition().duration(transitionPeriod).style("opacity", function() {
var currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? opacity : "1";
return currentDisplay;
});
d3.selectAll("circle:not([group='"+d.group+"'])")
.transition().duration(transitionPeriod).style("opacity",function() {
var currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? opacity : "1";
return currentDisplay;
});
d3.selectAll("text:not([group='"+d.group+"'])")
.transition().duration(transitionPeriod).style("opacity",function() {
var currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? opacity : "1";
return currentDisplay;
});
})
var node = g
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 14)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
var images = g.selectAll("image")
.data(graph.nodes)
.enter().append("image")
.attr("xlink:href",function(d){
var type = d.type,
typeIcon = "",
switch(type){
//assigns an image based on the subject type person, address, phone, ect.
}
return typeIcon;
})
// This is the label for each node
var text = g.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx",12)
.attr("dy",".35m")
.text(function(d) { return d.id;})
.attr("text-anchor", "middle")
.attr("group",function(d) {return d.group;} ) ;
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
//Used to drag the graph round the screen
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// This is the zoom handler
var zoom_handler = d3.zoom()
.scaleExtent([1/4, 4])
.on("zoom", zoom_actions);
//specify what to do when zoom event listener is triggered
function zoom_actions(){
g.attr("transform", d3.event.transform);
}
// initial scaling on the svg container - this means everything in it is scaled as well
svg.call(zoom_handler)
.call(zoom_handler.transform, d3.zoomIdentity.scale(0.9,0.9))
;
zoom_handler(svg);
};
And my ajax data looks like this
{
"nodes":[
{"id": "1", "group": "1", "type": "person", "name":"Jon Doe"},
{"id": "2", "group": "1", "type": "person", "name":"Jane Doe"}
//ect list of ~50
],
"links":[
{"source": "1", "target":"2"},
//ect list of ~50
]
}
I hope someone with more d3.js experience can point me in the right direction.
I'm posting this there in case someone else had the same problem. I solved my problem by breaking up the drawGraph function into smaller widgets.
I moved the following to the parent scope.
var svg = d3.select("svg"),
w = +svg.attr("width"),
h = +svg.attr("height),
node,
link;
var simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(60))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(w / 2, h / 2))
.force("attraceForce",d3.forceManyBody().strength(-900));
var color = d3.scaleOrdinal(d3.schemeCategory20);
Then within the drawGraph function I made the following changes.
function drawGraph(nodes,links){
var g = svg.append("g");
link = g.selectAll(".link").data(links,function(d){ return d.target.id; })
link = link.enter()
.append("line")
.attr("class","link")
.style("stroke-width", function(d) { return d.value; })
.style("stroke", "#999")
node = g.selectAll(".node").data(nodes,function(d){ return d.id; })
node = node.enter()
.append("g")
.attr("class","node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
node.append("circle").attr("r", 14).attr("fill",function(d){return color(d.group);})
node.append("text").attr("dy", -15)
.text(function(d){ return d.id; })
.attr("text-anchor","middle")
.style("fill", "#555");
node.append("image")
.attr("xlink:href",function(d){
var type = d.type,
typeIcon = "",
switch(type){
//assigns an image based on the subject type person, address, phone, ect.
}
return typeIcon;
})
.attr("x", -8)
.attr("y", -8)
.attr("height", 16)
.attr("width", 16);
simulation.nodes(nodes).on("tick", ticked);
simulation.force("link").links(links);
function zoom_actions(){
g.attr("transform", d3.event.transform);
};
var zoom_handler = d3.zoom()
.scaleExtent([1/4, 4])
.on("zoom", zoom_actions);
svg.call(zoom_handler).call(zoom_handler.transform, d3.zoomIdentity.scale(0.9,0.9));
zoom_handler(svg);
};
function ticked() {
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; });
node
.attr("transform", function(d) {
return "translate("+ d.x + ", " + d.y + ")";
});
};
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
I then added the following function which is used to setup the data and draw the graph.
function formatGraphData(relationships){
nodes = relationships.nodes;
links = relationships.links;
simulation.alpha(0.5).restart(); //<- needed to restart simulation and position nodes
drawGraph(nodes,links);
}
Then the ajax calls were updated to use formatGraphData instead of drawGraph.
I added the following to my css file
.links line{
stroke: #999;
}
.nodes circle{
stroke: #fff;
stroke-width: 1.5px;
}
Currently I am working one of FCC' Project and
I am trying to have node render with images from these flag sprites (https://www.flag-sprites.com/)
However, my nodes are not rendering, yet I see in my dev tool that are there.
Codepen - Not Working
const width = w - (margin.left + margin.right);
const height = h - (margin.top + margin.bottom);
let svg = d3.select("#canvas")
.append("svg")
.attr("id","chart")
.attr("width", w)
.attr("height", h)
let flagNodes = svg.append("div")
.classed("flag-nodes",true)
// .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d,i) {
return i;
}))
.force("charge", d3.forceManyBody().strength(-4))
.force("center", d3.forceCenter(width/2, height/2))
let node = flagNodes.append("div")
.selectAll(".flag-nodes")
.data(data.nodes)
.enter()
.append("div")
.attr("class", function(d,i){
return `flag flag-${d.code}`
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
)
let link = chart.append("g")
.classed("links",true)
.selectAll("line")
.data(data.links)
.enter()
.append("line")
node.append("title")
.text(function(d) { return d.country; });
simulation
.nodes(data.nodes)
.on("tick", ticked);
simulation.force("link")
.links(data.links);
//functions provided by D3.js
//
function ticked() {
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;});
node
.style("left", function(d) {
// console.log(d)
return d.x + 'px'
})
.style("top", function(d) {
return d.y + 'px'
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
The <div> elements are added to the HTML document and the links are added to <svg> element. They do not share the same coordinate system. Unfortunately you cannot add a <div> to an <svg>. It is probably easier to use another method for the flags instead of trying to coordinate both systems. You could use separate images for each flag and use <img> inside the <svg>.