Reputation: 578
I'm trying to make a force layout on D3 for network visualization with nodes and links. I've been using v3 a little bit, but I switched to v5 in order to be able to link nodes using node attributes instead of node indexes (which required additional steps in d3 v3).
I got this code
https://jsfiddle.net/lioneluranl/4fxpp2co/1/
var linkpath = ("links.csv");
var nodepath = ("nodes.csv");
var svg = d3.select("svg");
var width = svg.attr("width");
var height = svg.attr("height");
var simulation = d3.forceSimulation();
var nodes = [];
var links = [];
d3.csv(nodepath, function(d){
node = {
id: d.node,
group: d.group,
node: d.node
};
nodes.push(node);
d3.csv(linkpath, function(d){
link = {
source: d.source,
target: d.target,
type: d.type
};
links.push(link);
});
}).then( function() {
//console.log(links);
//console.log(nodes);
simulation
.force("link", d3.forceLink().id(function(d) { /*console.log(d);*/ return d.id; }))
.nodes(nodes)
.force("collide", d3.forceCollide().radius(10))
.force("r", d3.forceRadial(function(d) {
if(d.group === "compound"){
return 240;
} else { return d.group === "annotation" ? 0 : 100; }}))
// Create the link lines.
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke", "black")
.attr("stroke-width", 4)
.attr("class", function(d) { return "link " + d.type; });
// Create the node circles.
var node = svg.append("g")
.attr("class", "node")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 8)
.attr("class", function (d){
return d.group;
});
simulation.on("tick", ticked);
simulation.force("link").links(links);
function ticked() {
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
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; });
}
});
Which has been adapted from this
https://bl.ocks.org/mbostock/cd98bf52e9067e26945edd95e8cf6ef9
I have no problems drawing the nodes, but I'm unable to draw the links. Related documentation indicates that, as I think I'm doing, node attributes must be passed to links on force simulation, but I get this error:
TypeError: can't assign to property "vx" on "PF05257": not an object
Also, when doing this, the nodes won't behave as expected on the layout (the radial forces set won't work, see pictures attached), suggesting this link-by-node-attribute is messing with my simulation.
CSVs contain the following data:
nodes.csv:
node,group
C236103,compound
C327961,compound
C337527,compound
C376038,compound
C543486,compound
T24871,target
T27222,target
T33516,target
T33937,target
OG5_135897,annotation
PF01529,annotation
PF05257,annotation
PF11669,annotation
...
links.csv
source,target,type
T24871,PF05257,annotation
T27222,PF05257,annotation
T33516,PF01529,annotation
T33516,PF05257,annotation
T33516,PF11669,annotation
T33937,PF05257,annotation
T24871,C561727,bioactivity
T24871,C337527,bioactivity
T24871,C585910,bioactivity
...
Just for reference and data integrity sanity check, I got this working on d3 v3.
Any ideas?
Upvotes: 2
Views: 807
Reputation: 102174
Here is a constructive criticism: indent your code properly1.
The first time I read it I missed the problem, because of the incorrect indentation. But then, with the correct indentation, the issue is clear, have a look at this:
d3.csv(nodepath, function(d) {
node = {
id: d.node,
group: d.group,
node: d.node
};
nodes.push(node);
d3.csv(linkpath, function(d) {
link = {
source: d.source,
target: d.target,
type: d.type
};
links.push(link);
});
})
You cannot nest d3.csv
like this. The way your code is right now the second d3.csv
is part of the row function of the first d3.csv
, and that obviously won't work.
The correct approach here would be nesting the promises (which is an anti-pattern for some) or, even better, using Promise.all
(since there is no d3.queue
in v5):
var promises = [d3.csv("nodes.csv"), d3.csv("links.csv")];
Promise.all(promises).then(function(data) {
var links = data[1];
var nodes = data[0];
//rest of the code here
});
As an additional tip, you don't need to push the objects to arrays in the outer scope: just deal with the parameter inside then
.
Also, you don't need the row function for both CSVs, since your row functions are not doing anything right now (besides duplicating node
as id
they are simply returning the same object that you would have without them).
Here is a bl.ocks with your code and data, using Promise.all
:
1 Most text editors, like Sublime Text, have plugins for indentation. You can also find several good tools online, like this one for instance.
Upvotes: 3