I am following this tutorial: > http://bl.ocks.org/mbostock/4062045 for visualising a Force Directed Graph in D3 Javascript. The link above has the code and JSON file as well. I have two questions. How are the nodes linked? Here is the code for the links and nodes and their positions:
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.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
My second question: Could anyone please help me draw a sample of two nodes (circles) and one link between these two nodes so I can understand how this graph works. Your assistance would be very much appreciated.
Whether two nodes are linked or not is determined by the data. In particular, it contains lines such as
{"source":1,"target":0,"value":1}
which tell it which nodes (by index) to link. The indices refer to the list of nodes also given in the data. This data is given to D3 in this block:
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
So for each element in graph.links, a line will be added. At this point, the line doesn't have a start or end point, so it is not drawn -- only the element is added. Then, while the simulation is running, the start and end points of the lines are set based on the current state of the simulation. This is the code that you have in your question.
The following code would draw two circles and a link between them.
var svg = d3.select("body").append("svg").attr("width", 100).attr("height", 100);
svg.append("circle").attr("cx", 10).attr("cy", 10);
svg.append("circle").attr("cx", 90).attr("cy", 90);
svg.append("line").attr("x1", 10).attr("y1", 10).attr("x2", 90).attr("y2", 90);
Related
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; });
}
I am looking into this particular example
https://bl.ocks.org/mbostock/4062045
Here there is only one usage of value from the dataset, used to define stroke-width. How are some nodes further apart from each other than other nodes?
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.value); });
The cx,cy, x1,y1,x2,y2 is defined here
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; })
.attr("cy", function(d) { return d.y; });
}
what kind of data does d contain?
The simulation uses the nodes and links arrays from the JSON data. It fills in the position and speed of the nodes in the node objects. Each entry of the nodes arrays is assigned to a particular node-svg-circle, and it is d in all the callbacks.
The length of the links can be specified with the d3.forcelink().links() method (https://github.com/d3/d3-force#link_distance). In the example all links have the default preferred distance of 30 units. The other forces determine the actual length of the result link.
jsfiddle https://jsfiddle.net/z7ju3z1q/3/
The problem is, I can't make titles stick with groups with working simulation.
Changing line 76 from simulation.nodes(nodes); to simulation.nodes(node); breaks simulation (apparently), but makes title stick to circles. As I understand, the problem is that dragging working with circles but not with groups, for some reason. And that's is the problem Iwe been facing whole day.
I tried describing title like this (line 38)
var title = g.selectAll("text");
And then adding it to tick (line 84)
function ticked() {
title.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
}
As I understand, the problem is that dragging working with circles but not with groups, for some reason
You are manipulating the nodes on drag with cx and cy properties. g elements do not have these, so this will not achieve what you want, even if node contained groups rather than circles:
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
As noted,node = g.append("circle") means that you aren't actually manipulating the g elements anyways, which is why your circles move on tick or drag.
Instead, keep node a selection of g elements, and manipulate the transform property:
// the group representing each node:
node = node.enter().append("g").merge(node);
// the circle for each group
node.append("circle")
.classed('node', true).attr('id', id)
.text(id)
.attr("r", 25).attr("fill", function(d) { return color(d.id); });
// the text for each group
node.append("text")
.classed('text', true)
.attr("text-anchor", "start")
.attr("dx", 6).text(id).merge(node);
Then, on click or drag events, just update the transform:
Tick:
node.attr("transform",function(d) { return "translate("+d.x+","+d.y+")" ;});
Drag:
d.x = d3.event.x;
d.y = d3.event.y;
d3.select(this).attr("transform",function(d) { return "translate("+d.x+","+d.y+")" ;});
Here's an updated fiddle.
So this is my code, I have an array of arrays, and they contain an object with 4 points, so I can draw a line using svg, when I tested the code using only an array it worked fine, but I can't make it work with an array of arrays, any help would be deeply appreciated.
var circle = svgContainer.selectAll("svg").data(mainMt);
console.log(circle);
console.log("Linea");
var line = circle.data(function(d) { return d; })
.enter().append("line")
.attr("x1", function (d) { return d.x1; })
.attr("y1", function (d) { return d.y1; })
.attr("x2", function (d) { return d.x2; })
.attr("y2", function (d) { return d.y2; })
.attr("stroke-width", 2)
.attr("stroke", "black");
console.log(line);
There are two things missing -- first, you need to operate on the enter selection of the top-level selection and second, select things for the nested selection:
svgContainer
.selectAll("g.lines")
.data(mainMt)
.enter().append("g")
.attr("class", "lines")
.selectAll("line").data(function(d) { return d; })
.enter()
.append("line")
...
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