I would like to add separate arrows (one for the path from node A to B, and the other for B to A) to a d3 graph that I have made (see the Plunker version here), instead of adding a single arrow with two arrows at each end.
I have tried the code here, by changing my 'tick' function, as well as defining a path variable to be as below:
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
However, this seems to stop the nodes from displaying, as well as the zoom/filtering functionality.
Does anyone know how I can stop this happening?
Best,
Ben
Related
I want to centralize the node when I clicked it, so I used the following code:
node.on("click", function(d){
var cX = width/2;
var cY = height/2;
var dx = cX - d.x;
var dy = cY - d.y;
link.attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; });
node.attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; });
});
It works for the "normal node", but when changed the node to image, the link is moved while the node (image) is still here.
Here is my example: http://talk.huacishu.com/t11.html
I know a little bit about d3js\javascript, anyone can tell me where is wrong?
I finally made it by another way :
Using "var g_container = svg.append(g) "
to append a "g" label(g_container), and move the "g_container" to centralize the clicked node.
here is the final project:
https://myaisv.github.io/curry/renlifang/connan/t1.html
I have this function to create a curve between two points
var amountOfCurve = (d.noOfSameConnections+1); //between 0 and 10
var dy = d.target.x - d.source.x,
dx = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy) *(amountOfCurve);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + (d.target.x) + "," + (d.target.y);
Some links have both the same source and target. I solve this in the noOfSameConnections. But what I want is instead of different sized curve, as there can only be a maximum of two links between two nodes, I want the link to curve the other way. So I would do something like this :
if(d.noOfSameConnections === 1){
//curve one way
} else {
//curve the other
}
But I can't seem to work out how to switch the curve around :(
The direction of the arc can be controlled with the sweep flag.
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
var d = {
source: {
x: 10,
y: 10
},
target: {
x: 490,
y: 490
}
};
var dy = d.target.x - d.source.x,
dx = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy) * 0.8;
var largeSweep = 0;
var sweep = 1;
svg.append("path")
.attr("d", "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 " + largeSweep + "," + sweep + " " + d.target.x + "," + d.target.y)
.style("fill", "none")
.style("stroke", "steelblue")
.style("stoke-width", 2);
largeSweep = 0;
sweep = 0;
svg.append("path")
.attr("d", "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 " + largeSweep + "," + sweep + " " + d.target.x + "," + d.target.y)
.style("fill", "none")
.style("stroke", "orange")
.style("stoke-width", 2);
</script>
</body>
</html>
With the Mobile Patent Suit example by Mike Bostock as an example, I was able to re-create using my own data, but I want to take it a step further and dynamically update the node relationships on a drop-down change. The code works almost every time, but occasionally I get a single node with no relationships (see image below) even though data are available. I think I may be doing something wrong when clearing and then updating the nodes on a selection change, but it's strange to me that is works some of the time. Any help is greatly appreciated.
http://jsfiddle.net/calanoue/r6BRr/
var selectedCode = '0';
function updateNodes() {
// Update the links and the nodes any time the data changes
svg.selectAll('g').remove();
links = allData[0][selectedCode];
// Compute the distinct nodes from the links.
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(80)
.charge(-400)
.on("tick", tick)
.start();
path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", "link relationship")
.attr("marker-end", "url(#relationship)");
circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr('class', "generic")
.attr("r", 8)
.call(force.drag);
}
function tick() {
// Use elliptical arc path segments to doubly-encode directionality.
path.attr("d", linkArc);
circle.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
function clearNodes() {
nodes = {};
links = [];
force.start();
d3.timer(force.resume);
}
function codeChange() {
selectedCode = this.value;
clearNodes();
updateNodes();
}
Missing Node Relationship Image:
The problem you were having is that your original data, allData, was being changed by the computation of links and nodes at each data change iteration. After all, you set links = cloneData[0][selectedCode];. You need to clone allData before each data change.
function codeChange() {
selectedCode = this.value;
cloneData = clone(allData); // added
clearNodes();
updateNodes();
}
Here is a FIDDLE with all the changes to make it work. I borrowed the clone function from this SO question.
NOTE: I also removed one of your dropdown choices since you only have 3 sets of data in allData. That was not the cause of your problems, per above, but it was throwing errors whenever you selected the inexistent set.
Here is my current fiddle. I'm using foreignObjects to define custom icons for nodes (the fiddle is just using a ? but locally I'm using font-awesome icons). This works great, however the problem is that the arrows on the paths point to the top-left corner of the element. I've tried changing toying with many of the existing parameters and have looked through the API documentation but am unable to find a solution. I'm guessing I could do some complicated math in the code below, but I'm hoping for a parameter that can set some sort of radius for the end of the path.
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
The only documentation I found on paths was geo-paths. I tried using pointRadius anyway but it didn't seem to do anything at all. Here is the path definition:
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function (d) { return "link " + d.type; })
.attr("marker-end", function (d) { return "url(#" + d.type + ")"; });
You can use the transform attribute to adjust the position:
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("transform", "translate(10,10)")
.attr("class", function (d) { return "link " + d.type; })
.attr("marker-end", function (d) { return "url(#" + d.type + ")"; });
I'm trying to get my D3 network to freeze after it reaches a nice layout (alpha reaches 0). I want the force to stop completely, even when a node is dragged (the user should be able to rearrange the nodes manually). I think I know how to do the second part of this, by modifying the functions that are called on mousedown and mouseup for the nodes. However, I can't get the original layout and freezing to work.
I've looked at the examples for "static" force layouts, where the network is displayed only after the layout is completed. However, I want the network to display as it's reaching the stable layout. I added this to the end of the function that draws the network:
while (force.alpha() >0.005) {
force.tick();
}
force.stop();
With this addition, the network doesn't display until it gets to force.stop(). Does anyone know how I can get it to display while it's "ticking"?
EDIT: Here's my implementation of the tick function:
function tick(e) {
console.log(force.alpha());
if (force.alpha() <0.05) {
force.stop();
}
var h = svgH;
if (e.alpha < 0.05) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length;
while (++i < n) {
q.visit(collide(nodes[i], e.alpha));
}
}
path.attr("d", function(d) {
// Total difference in x and y from source to target
diffX = d.target.x - d.source.x;
diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
offsetX = (diffX * d.target.radius) / pathLength;
offsetY = (diffY * d.target.radius) / pathLength;
if (d.target.y < d.source.y) {
var avgY = (d.target.y + d.source.y)/2;
if (d.target.fixed != true) {
d.target.y = avgY;
}
if (d.source.fixed != true) {
d.source.y = avgY;
}
}
return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
// Keep circles within bounds of screen
var r = 6;
circle.attr("cx", function(d) { return d.x = Math.max(r + d.radius, Math.min(w - r, d.x)); })
.attr("cy", function(d) {
return d.y = Math.max(d.radius, Math.min(h - d.radius, d.y));
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
Check the stop condition in the tick event handler -- this way you can redraw the network on each tick and stop.