I've a problem with creating multiple force layout graphs using d3 and reading data from a json file. I use a for loop to iterate over the graphs, create a separate div containing a svg for each. The problem is, that the force layout is only applied to the last one created, so basically the others just show a dot in the upper left corner. I could solve it partly by putting a for loop at the end of each iteration, but I still lose the interaction capabilities of the separate figures.
Find the code below, thanks in advance.
Cheers, Michael
var color = d3.scale.category20();
var force = new Array();
var div = new Array();
var svg = new Array();
var graph = new Array();
var link;
var node;
var width = 360;
var height = 360;
var brush = new Array();
var shiftKey;
var count = 0;
//loop through the different subsystems in the json-file
for(name_subsystem in graphs) {
//add a div for each subsystem
div[count] = document.createElement("div");
div[count].style.width = "360px";
div[count].style.height = "360px";
div[count].style.cssFloat="left";
div[count].id = name_subsystem;
document.body.appendChild(div[count]);
//force is called. all attributes with default values are noted. see API reference on github.
force[count] = d3.layout.force()
.size([width, height])
.linkDistance(20)
.linkStrength(1)
.friction(0.9)
.charge(-30)
.theta(0.8)
.gravity(0.1);
div[count].appendChild(document.createTextNode(name_subsystem));
//create the svg rectangle in which other elements can be visualised
svg[count] = d3.select("#"+name_subsystem)
.on("keydown.brush", keydown)
.on("keyup.brush", keyup)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("id",name_subsystem);
brush[count] = svg[count].append("g")
.datum(function() { return {selected: false, previouslySelected: false}; })
.attr("class", "brush");
//force is started
force[count]
.nodes(graphs[name_subsystem].nodes)
.links(graphs[name_subsystem].links)
.start();
//link elements are called, joined with the data, and links are created for each link object in links
link = svg[count].selectAll(".link")
.data(graphs[name_subsystem].links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.thickness); })
.style("stroke", function(d){
if (d.linktype === 'reactant'){
return "black";
} else {
return "red";
}
});
//node elements are called, joined with the data, and circles are created for each node object in nodes
node = svg[count].selectAll(".node")
.data(graphs[name_subsystem].nodes)
.enter().append("circle")
.attr("class", "node")
//radius
.attr("r", 5)
//fill
.attr("fill", function(d) {
if (d.type === 'metabolite') {
return "blue";
} else {
return "red";
}
})
.on("mousedown", function(d) {
if (!d.selected) { // Don't deselect on shift-drag.
if (!shiftKey) node.classed("selected", function(p) { return p.selected = d === p; });
else d3.select(this).classed("selected", d.selected = true);
}
})
.on("mouseup", function(d) {
if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false);
})
.call(force[count].drag()
.on("dragstart",function dragstart(d){
d.fixed=true;
d3.select(this).classed("fixed",true);
})
);
//gives titles to nodes. i do not know why this is separated from the first node calling.
node.append("title")
.text(function(d) { return d.name; });
//enable brushing of the network
brush[count].call(d3.svg.brush()
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height]))
.on("brushstart", function(d) {
node.each(function(d) { d.previouslySelected = shiftKey && d.selected; });
})
.on("brush", function() {
var extent = d3.event.target.extent();
node.classed("selected", function(d) {
return d.selected = d.previouslySelected ^
(extent[0][0] <= d.x && d.x < extent[1][0]
&& extent[0][1] <= d.y && d.y < extent[1][1]);
});
})
.on("brushend", function() {
d3.event.target.clear();
d3.select(this).call(d3.event.target);
})
);
//applies force per step or 'tick'.
force[count].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; });
});
//with this it works partly
//for (var i = 0; i < 5000; ++i)force[count].tick();
count++;
};
function keydown() {
if (!d3.event.metaKey) switch (d3.event.keyCode) {
case 38: nudge( 0, -1); break; // UP
case 40: nudge( 0, +1); break; // DOWN
case 37: nudge(-1, 0); break; // LEFT
case 39: nudge(+1, 0); break; // RIGHT
}
shiftKey = d3.event.shiftKey || d3.event.metaKey;
}
function keyup() {
shiftKey = d3.event.shiftKey || d3.event.metaKey;
}
edit: updated the code after the comments, still the same problem.
i am working on force layout only, with many graphs at same time.
1 You don't need to have a count variable for each graph.
2 Don't make these variable(force, svg, graph) as array. There is no need for it. just declare them above as (var svg;) and further on. As you call the function, it automatically makes its different copy and DOM maintain them separately. So every variable you are using in graph, make it declare on top of function.
3 You are drawing all the graphs at same time, so as the new one is called, the previous one stops from being making on svg, that's why only last graph built successfully. So draw them after small time intervals.
Here the code I finally used with the help of the comments above, maybe helpful for others as well:
Note: graphs is the name of the variable in my json file. You need to include the d3-library.