I'm a new user of D3 version 4, and I'm having problems adding a fixed node feature to a force directed layout. I believe this was achieved in v3 by setting d.fixed to true, however I'm having issues translating this into something compatible with v4. I've tried adding simulation.fix, but can't integrate this into my existing code without fixing nodes from the start of the simulation.
Ideally, I would like to add a double-click listener that would fix a node in place, and allow it to be positioned by a drag function, and then restore the simulation forces if double clicked again. Thank you in advance for the help.
Here is my js:
<!--load svg-->
<svg width="1500" height="600"></svg>
<!--begin javascript for d3 forced layout-->
<script>
var svgNetwork = d3.select("svg"),
width = +svgNetwork.attr("width"),
height = +svgNetwork.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(data1) { return data1.id; }))
.force("charge", d3.forceManyBody().strength(-1500))
.force("center", d3.forceCenter(width / 2, height / 2));
//load the node csv data and link csv data together - open node(open link(funtions for making chart))
d3.csv("nodes.csv", function (error1, data1) {
if (error1) throw error1;
console.log(data1);
d3.csv("lines.csv", function (error2, data2) {
if (error2) throw error2;
console.log(data2);
data1.forEach(function (data1){
data1.group = +data1.group;
});
var lower = d3.min(data1, function(data1) {return data1.group;});
var upper = d3.max(data1, function(data1) {return data1.group;});
var color = d3.scaleLinear()
.domain([lower, 0, upper])
.range(["#2E64FE", "#E6E6E6", "red"]);
var link = svgNetwork.append("g")
.attr("class", "links")
.selectAll("line")
.data(data2)
.enter()
.append("line")
.attr("stroke-width", function(data2) { return Math.sqrt(data2.value); })
.attr("fill", "#777")
.attr("stroke-opacity", "0.6");
var node = svgNetwork.selectAll(".node")
.data(data1)
.enter()
.append("g");
var circle = node.append("circle")
.attr("id", function(data1) {return data1.id;})
.attr("r", function(data1) {return data1.rad;})
.attr("fill", function(data1) { return color(data1.group); })
.style("stroke", function(data1) { return color(data1.group); })
.style("stroke-width", "10px")
.style("stroke-opacity", "0.9");
var label = node.append("svg:text")
.text(function (data1) { return data1.id; })
.style("text-anchor", "middle")
.style("fill", "#000000")
.style("font-family", "Arial")
.style("font-size", "0.8em")
.style("font-weight", "bold");
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
simulation
.nodes(data1)
.on("tick", ticked);
simulation.force("link")
.links(data2);
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;
}
Here is an example of my node.csv file and lines.csv file:
id group rad
a 0.168316947 0.288907878
b -0.38499088 1.012210504
c -0.548386797 1.301376974
d -0.215565786 2.456429671
e -0.756094177 6.409396582
f -0.538867892 1.804950731
g -0.325232806 0.518895927
h 0.686157994 1.011850971
i -0.723155438 5.853700074
j 2.008089674 2.73207752
k -0.358621917 2.040722107
l -0.393305984 3.221637083
m -0.676289998 1.598250699
n -0.950808451 26.26021586
o 0.134589658 0.270633823
p -0.521333199 6.216421369
q 1.628300116 2.293471337
r 0.62673 2
s -0.843711093 40.86067523
source target value
b a 20
c a 20
d a 20
e a 20
f a 20
g a 20
h a 20
i a 20
j a 20
k a 20
l a 20
m a 20
n a 20
o a 20
p a 20
q a 20
r a 20
s a 20
Not sure if the answer is needed but I was able to make Nodes sticky, actually it was pretty simple fix in your code and I learnt it when I reviewed this Issue on GitHub - Issue #35
The idea of fixing a node is very simple now in v4 of d3 library, in your above code just comment following two lines:
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
//d.fx = null;
//d.fy = null;
}
Theory is, to fix the node, we need to set node.fx
& node.fy
which your code is actually setting in dragged()
function definition.
Hope it helps, as it does to me.
The same is also mentioned in the documentation here - D3 V4 - API
To fix a node in a given position, you may specify two additional properties:
fx
- the node’s fixed x-positionfy
- the node’s fixed y-position
Plus the funcionality to release the node after double click will be:
var node = g.selectAll(".nodes")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", "black")
// Next two lines -> Pin down functionality
.on('dblclick', releasenode)
.call(node_drag);
var node_drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
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);
// Allows NODE FIXING
// d.fx = null;
// d.fy = null;
}
function releasenode(d) {
d.fx = null;
d.fy = null;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With