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 would like to add zoom on my D3 map but when I click on a node, drag and drop this, my cursor fix all elements. I added .call(d3.zoom()) but
code component from angular 4 :
ngOnInit(){
}
ngAfterViewInit(){
//this.svg = d3.select("svg");
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function (event) {
let g = d3.select('svg > g');
g.attr("transform", d3.event.transform);
}))
.append("g");
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);
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(miserables);
}
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){
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); })
.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", ()=>{return this.ticked()});
this.simulation.force("link")
.links(graph.links);
}
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;
}
dragstarted(d) {
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
ngOnDestroy(){
}
If I replace this :
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
by this :
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function (event) {
let g = d3.select('svg > g');
g.attr("transform", d3.event.transform);
}))
.append("g");
Zoom works, but I have a problem when I selected a node to drag and drop, the cursor fix on all elements
My problem :
https://angular-tspcs9.stackblitz.io/
To edit :
https://stackblitz.com/edit/angular-tspcs9
I added :
.on("mousedown", function() {
d3.event.stopPropagation();
});
on my svg declaration :
this.svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function (event) {
let g = d3.select('svg > g');
g.attr("transform", d3.event.transform);
}))
.append("g")
.on("mousedown", function() {
d3.event.stopPropagation();
});
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 trying to make a d3 force graph, however I can't manage to let the labels work. Sometimes text pops up in the top of the screen. How do I assign each node a label (the label being its id).
JSFIDDLE
This is what i've tried:
var label = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.text("test")
And an extra question, how do I get arrowheads on my lines?
Group circle and text inside a group element. You can use marker-end property to add end markers to the line.
There are marker-mid and marker-start configs too.
var graph = JSON.parse('{"nodes":[{"id":"0","color":"#4444E5"},{"id":"1","color":"#4444E5"},{"id":"2","color":"#CCCCCC"},{"id":"3","color":"#CCCCCC"},{"id":"4","color":"#CCCCCC"},{"id":"5","color":"#CCCCCC"},{"id":"6","color":"#CCCCCC"},{"id":"7","color":"#CCCCCC"},{"id":"8","color":"#CCCCCC"},{"id":"9","color":"#CCCCCC"},{"id":"10","color":"#cc0000"},{"id":"11","color":"#CCCCCC"},{"id":"12","color":"#CCCCCC"},{"id":"13","color":"#CCCCCC"},{"id":"14","color":"#CCCCCC"},{"id":"15","color":"#cc0000"}],"links":[{"source":1,"target":15,"weight":-0.7582412523901727},{"source":0,"target":6,"weight":-0.08179822732917646},{"source":6,"target":10,"weight":0.0939757437427291},{"source":3,"target":4,"weight":0.436380368086641},{"source":4,"target":5,"weight":-0.5385567058738547},{"source":1,"target":2,"weight":-0.277729004201837},{"source":2,"target":3,"weight":0.7675128737907505},{"source":13,"target":14,"weight":0.5519067984674436},{"source":14,"target":15,"weight":0.832660697502495},{"source":5,"target":7,"weight":0.8607887414383458},{"source":7,"target":8,"weight":-0.8965760877371078},{"source":8,"target":9,"weight":-0.2488791800975232},{"source":9,"target":10,"weight":-0.646075500567235},{"source":12,"target":13,"weight":0.40622804770087395},{"source":0,"target":11,"weight":0.24806004723386413},{"source":11,"target":12,"weight":0.8035303834469385}]}');
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var defs = svg.append("defs")
var marker = defs
.append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
marker.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("class", "arrowHead");
var 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));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) {
return Math.sqrt(d.weight);
}).attr("marker-end", "url(#arrow)")
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("g");
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) {
return d3.color(d.color);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var label = node.append("text")
.attr("class", "label")
.text("test")
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("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;
}
line {
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="600"></svg>
Your texts are not part of the simulation. Besides that, when you do...
var label = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("text")
... your "enter" selection will have one element less, because there is already a <g> element with class node in that SVG.
Solution: append both circles and texts to groups:
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("foo")
.data(graph.nodes)
.enter().append("g");
var circles = node.append("circle")
.attr("r", 5)
.attr("fill", function(d) {
return d3.color(d.color);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var label = node.append("text")
.attr("class", "label")
.text("test");
And use transform in the ticked function:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
Here is your updated code:
var graph = JSON.parse('{"nodes":[{"id":"0","color":"#4444E5"},{"id":"1","color":"#4444E5"},{"id":"2","color":"#CCCCCC"},{"id":"3","color":"#CCCCCC"},{"id":"4","color":"#CCCCCC"},{"id":"5","color":"#CCCCCC"},{"id":"6","color":"#CCCCCC"},{"id":"7","color":"#CCCCCC"},{"id":"8","color":"#CCCCCC"},{"id":"9","color":"#CCCCCC"},{"id":"10","color":"#cc0000"},{"id":"11","color":"#CCCCCC"},{"id":"12","color":"#CCCCCC"},{"id":"13","color":"#CCCCCC"},{"id":"14","color":"#CCCCCC"},{"id":"15","color":"#cc0000"}],"links":[{"source":1,"target":15,"weight":-0.7582412523901727},{"source":0,"target":6,"weight":-0.08179822732917646},{"source":6,"target":10,"weight":0.0939757437427291},{"source":3,"target":4,"weight":0.436380368086641},{"source":4,"target":5,"weight":-0.5385567058738547},{"source":1,"target":2,"weight":-0.277729004201837},{"source":2,"target":3,"weight":0.7675128737907505},{"source":13,"target":14,"weight":0.5519067984674436},{"source":14,"target":15,"weight":0.832660697502495},{"source":5,"target":7,"weight":0.8607887414383458},{"source":7,"target":8,"weight":-0.8965760877371078},{"source":8,"target":9,"weight":-0.2488791800975232},{"source":9,"target":10,"weight":-0.646075500567235},{"source":12,"target":13,"weight":0.40622804770087395},{"source":0,"target":11,"weight":0.24806004723386413},{"source":11,"target":12,"weight":0.8035303834469385}]}');
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var 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));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) {
return Math.sqrt(d.weight);
})
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("foo")
.data(graph.nodes)
.enter().append("g");
var circles = node.append("circle")
.attr("r", 5)
.attr("fill", function(d) {
return d3.color(d.color);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var label = node.append("text")
.attr("class", "label")
.text("test");
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("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;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="400"></svg>
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>.