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

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;
});

Related

Apply curveStep to a path in d3 forceDirected layout

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)
})
}

D3 JS - How to draw horizontal force graph

I am trying to create a force graph as shown in below image. I am checking the graph given at this link force tree. This tree is vertical. I want it horizontal so that I can achieve the graph shown in the image. I tried to play with the code but was not able to make it horizontal. Is there any way I can achieve the graph shown in the image.
desired force graph
Turning the tree horizontal is actually quite simple in the given example.
The tick function is responsible for assigning positions to each node, and it modifies the y value of the nodes between levels. Simply change it to modify the x value of the nodes.
Like so:
function tick(e) {
var k = 6 * e.alpha;
// Push sources up and targets down to form a weak tree.
link
// Swapped here from y to x
.each(function(d) { d.source.x -= k, d.target.x += k; })
.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; });
}

How to avoid marker and node overlaping in cola.js / d3.js

i am working around a cola.js example enter link description here
and i added markers (arrows) a the end of the links. However, since the nodes are rectangular are overlaping the arrows. I tried to fix it by changing 'refX' of marker but is not looking good.
The code of the marker is :
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 30)
//.attr("refY", -1.5)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
and then add it in the end of link:
` cola.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; })
.attr("marker-end","url(#end-arrow)");
`
If i may recommend another way to create your edges.
Following this example here - disclaimer it seems the actual example is broken due to what im assuming is a typo in the code.
long story short. the important part is this inside your cola.on("tick" function(){
link.each(function (d) {
d.route = cola.makeEdgeBetween(d.source.innerBounds, d.target.innerBounds, 5);
});
link.attr("x1", d => d.route.sourceIntersection.x)
.attr("y1", d => d.route.sourceIntersection.y)
.attr("x2", d => d.route.arrowStart.x)
.attr("y2", d => d.route.arrowStart.y);
the third argument (5) for makeEdgeBetween, if i'm not mistaken, is spacing to take into account your arrowhead length, if you look at the source it should be called ah. And im pretty sure the makeEdgeBetween function will create the edge up until the rectangular boundary of the node, instead of the centre
Had the same issue, but I took a different approach. I did not like having the arrowhead only on the sides, so I made a function to calculate the relative positions of the target and source nodes and place the link targets accordingly.
function calculateRelativePosition(d){
let relative = {"x2":0,"y2":0};
const pixels = 3;
const relativex = d.source.x - d.target.x;
const relativey = d.source.y - d.target.y;
if(Math.abs(relativex) > Math.abs(relativey)){
if((d.source.x - d.target.x)>0)
{
relative.x2 = d.target.x + d.target.width /2 - pad +pixels;
relative.y2 = d.target.y;
return relative;
}
else
{
relative.x2 = d.target.x- d.target.width/2 + pad - pixels;
relative.y2 = d.target.y;
return relative;
}
}
if((d.source.y - d.target.y)>0)
{
relative.x2 = d.target.x
relative.y2 = d.target.y + d.target.height /2 - pad + pixels
return relative;
}
else
{
relative.x2 = relative.x2 = d.target.x;
relative.y2 = d.target.y - d.target.height/2 + pad - pixels
return relative;
}
}
Then on the cola.on("tick")
cola.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 calculateRelativePosition(d).x2 })
.attr("y2", function (d) { return calculateRelativePosition(d).y2 });
The method will compare the relative position of the nodes and will place the arrowhead in one of the 4 sides of the rectangle, the one with the least angular distance to the other node.

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.

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/

Categories