Apply curveStep to a path in d3 forceDirected layout - javascript

I need to convert a forceDirected layout to use stepped path curves instead of point to point straight lines. Typical links connecting nodes use this method.
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+6; })
.attr("cy", function(d) { return d.y-6; });
}
Here is an example of a forceDirected as a use case
https://www.d3-graph-gallery.com/graph/network_basic.html
Here a rough idea of how I want the links to be rendered shown in red.
Here is a method I am trying to use as a solution
https://www.geeksforgeeks.org/d3-js-curvestep-method/
and here is a snippet of code I am testing based on the linked example above without much success
var line = d3.line()
.x((d) => d.x)
.y((d) => d.y)
.curve(d3.curveStep);
function addConnections(o){
// o is link.d from existing links between nodes
var ldata = []
var d = {x: o.source.x,y:o.source.y}
ldata.push(d)
var d = {x: o.target.x,y:o.target.y}
ldata.push(d)
var steps = svg
.selectAll("path")
.data(ldata)
.enter()
.append("path")
.attr("d",function(d){
console.log(d)
line(d)
})
.classed("steps",true)
}

Here is the final simple solution I came up with that works for generating stepped curves as links between nodes in forceDirected layout
line = d3.line()
.x((d) => d.x)
.y((d) => d.y + 5)
.curve(d3.curveStepAfter)
function ticked(){
link
.attr("d", function (d) {
a.push({x: d.source.x, y: d.source.y});
a.push({x: d.target.x, y: d.target.y});
return line(a)
})
}

Related

Force-Layout links all being removed when updating with new data (d3 v 4.0)

This is my first question here so bear with me...
I've made a Force-Directed graph using the d3 inbuilt force layout. This works find on the initial load, but when I try to reload with new data (using onchange with a select element), all the links disappear from the force layout.
The data seems to be updating correctly, in that the correct links are still present in the data, I just can't understand why the links aren't coming through as lines in the svg element.
I've made a js fiddle to show how I'm doing it:
https://jsfiddle.net/onlyskin/f5a4uxje/
(run and then select either 'size' or 'colour' from the dropdown - the expected behaviour is for the appropriate nodes and links to disappear).
The update function looks like this:
function update() {
var pointGroup = nodeGroup.selectAll('.node')
.data(data.nodes, function(d) { return d ? d.id : this.id; });
pointGroup.exit().remove();
var newNodeGroups = pointGroup.enter().append('g')
.attr('class', 'node');
newNodeGroups.append('circle')
.attr('r', function(d) { return circleRadius[d.type] });
newNodeGroups.append('text')
.text(function(d) { return d.id; })
.attr('class', function(d) { return d.type; });
var allLinks = linkGroup.selectAll('line')
.data(data.links, function(d) { return d.source+'-'+d.target; });
allLinks.exit().remove();
newLinks = allLinks.enter().append('line')
.attr('class', 'link');
var circles = nodeGroup.selectAll('circle');
var text = nodeGroup.selectAll('text');
var lines = linkGroup.selectAll('line');
function ticked() {
lines
.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; });
circles
.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; });
}
simulation
.on('tick', ticked)
.restart();
};

Moving fixed nodes in d3 force layout

I am using D3's forced layout to display a graph. Now, I require that the nodes change their positions when any node is clicked.
I looked up other related StackOverflow questions but that didn't help me.
The code for render is as follows :
var render = function(graph){
/* var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment pleaseā€¦");*/
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links);
//Enter phase for links.
link.enter().append("line");
//Update phase for links
link
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes,function(d){return d.name;});
//Enter phase for nodes
var node_g = node.enter()
.append("g")
.attr("class","node")
.on("dblclick",nodeClick)
.call(force.drag);
//Update phase for nodes
node_g.append("text")
.attr("class","NodeLabel")
.text(function(d){
return d.name;
});
var nodeCirlce = node_g.append("circle");
nodeCirlce
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
node_g.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
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_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
});
//TODO : Add attr change for node text as well.
});
And the code for the node click handler looks like this :
var nodeClick = function(d,i){
//Translate the graph to center around the selected node.
var x_trans = x_cent - d.x;
var y_trans = y_cent - d.y;
var nodes = oldGraph.nodes;
for(var i=0;i<nodes.length;i++){
var node = nodes[i];
node.x = node.x + 1000;
node.y = node.y + 1000;
node.fixed = true;
}
//oldGraph.nodes = updateNodes(nodes,oldGraph.links);
render(oldGraph);
//setTimeout(function(){layout("json/HUMAN-1g.json");},000);
};
However, the node positions don't get updated.
After changing the data, you need to run the code that updates the positions in the DOM. This is exactly what you have in the tick event handler function:
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_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
I recommend pulling this out into a separate function and then setting it as the tick handler function and calling it from your nodeClick() function. To be clear, you don't need to call render() from the nodeClick() function.

Line passing through another point before connecting to node d3.js

The link between two nodes enter as a straight line , i want the line to bend at a point and then move to that node . How can i do ?
i dont need a curve , basically a line passing through one point and then moving towards the other node.
I use
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;
});
Use svg path.
path.attr("d", function (d) {
var sX = d.source.x, sY =d.source.y;
var tX = d.target.x, tY =d.target.y;
var mX = (d.source.x+d.target.x)/2+45, mY =(d.source.y+d.target.y)/2+45; //Third point
return "M "+sX+","+sY+" L"+mX+","+mY+" L"+tX+","+tY;
});

Weird behaviour of Fisheye Distortion plugin

Hi I would like to use Fisheye Distortion plugin for my force-directed graph in d3.js, but when I want to apply this plugin, behaviour of graph is weird. I am new in d3.js and not good at computer graphics.
complete sample in jsfiddle
var fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
// graph - variable which represents whole graph
graph.svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
d3.select("svg").selectAll("circle").each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
d3.select("svg").selectAll("line").attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
Weird behaviour I mean the nodes of graph disappear (are hidden) after mouseover action.
The problem is that you were using the code to add cx and cy to circles, but your circles were actually enclosed inside nodeElements which were transformed into place.
Hence, changing the fisheye code to the following solves the problem:
graph.svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
// Change transform on the .node
d3.select("svg").selectAll(".node")
.each(function(d) { d.fisheye = fisheye({ x: graph.x(d.x), y: graph.y(d.y) }); console.log(d.fisheye, d); })
.attr("transform", function (d) { return "translate(" + d.fisheye.x + "," + d.fisheye.y + ")"; })
// Now change the 'r'adius on the circles within
// One can also scale the font of the text inside nodeElements here
.select("circle")
.attr("r", function(d) { return 15 * graph.nodeSizeFactor * d.fisheye.z; });
d3.select("svg").selectAll("line")
.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
Note that I have also applied the proper scales graph.x and graph.y for the transform attribute and 15 * graph.nodeSizeFactor for the radius of the circles (instead of 4.5).
Working Demo: http://jsfiddle.net/90u4sjzm/23/

D3 force layout nodes attributes not updated properly

I have network data for different days which I plot as a force-directed graph for each single day. When I press a button, the network is partially (leaving nodes are removed, new nodes are drawn) updated to the following day. Everything is working fine except for one thing.
For every new day I update some attributes of my nodes-array from my data (e.g. degree of the node). This also works fine, since I can see that the attributes have been updated correctly when I just look at my nodes-array after switching to the next day. However the command
`.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
is not conducted with the new attributes and the radius of the nodes do not represent by the degree of the node at the date the graph show.
How can I update my graph such that the new values for Degree are used to define the radius of the nodes?
Here is my function start(), which I call after manipulating my data to plot the graph:
function start() {
var force = d3.layout.force()
.charge(-130)
.linkDistance(230)
.size([width, height]);
force.nodes(nodes)
.links(edges)
linkOP = linkOP.data(edges, function(d) { return d.source.id + "-" + d.target.id; });
linkOP.enter().insert("line", ".node1")
.attr("class", "link1")
.style("stroke-width", function(d) {
return 2*d.weight.weight;
});
linkOP.exit()
.remove();
nodeOP = nodeOP.data(nodes, function(d) { return d.id;});
nodeOP.enter()
.append("g")
.append("circle")
.attr("r", function(d) { return 2*d.Degree+10; })
.attr("class", "node")
.style("fill", function(d) { return color(d.bipartite); });
nodeOP.append("title").text(function(d) { return d.name; });
nodeOP.call(force.drag);
nodeOP.append("text")
.text(function(d) { return d.id; });
nodeOP.moveToFront();
nodeOP.exit().remove();
force.start();
clean();
force.on("tick", function() {
linkOP.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; });
nodeOP.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
nodeOP.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
}
I know this is a very simple question... Thank you very much for your help!
Your code doesn't have an update to the radius. Everything called by .enter() only occur for new items. So because you only have the line
.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
Inside the .enter that portion only occurs on the new nodes.
I built a jsfiddle on enter, update, exit. Here: http://jsfiddle.net/TheMcMurder/H3HTe/
I hope that helps

Categories