Multiple graphs of power layouts with d3 in separate svg / div's

I had a problem creating multiple force build graphs using d3 and reading data from a json file. I use a for loop to iterate over graphs, creating a separate div containing svg for each. The problem is that the force layout applies only to the last one created, so basically the rest just show a point in the upper left corner. I could solve this in part by putting a for loop at the end of each iteration, but I still lose the ability to interact with individual 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] <= dx && dx < extent[1][0] && extent[0][1] <= dy && dy < 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 dx; }) .attr("cy", function(d) { return dy; }); }); //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: update the code after comments, still the same problem.

+4
source share
2 answers

Here, the code that I finally used using the comments above can be useful for others too:

 <script type="text/javascript" src="d3_splitted_var.json"></script> <script> function draw_graphs(name_subsystem){ var force; var div; var svg; var link; var node; var width = 360; var height = 360; var r=5; var brush = new Array(); var shiftKey; //add a div for each subsystem div = document.createElement("div"); div.style.width = "360px"; div.style.height = "360px"; div.style.cssFloat="left"; div.id = name_subsystem; document.body.appendChild(div); force = d3.layout.force() .size([width, height]) .linkDistance(20) .linkStrength(1) .friction(0.9) .charge(-50) .theta(0.8) .gravity(0.1); div.appendChild(document.createTextNode(name_subsystem)); //create the svg rectangle in which other elements can be visualised svg = d3.select("#"+name_subsystem) .append("svg") .attr("width", width) .attr("height", height) .attr("id",name_subsystem); //force is started force .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.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.selectAll(".node") .data(graphs[name_subsystem].nodes) .enter().append("circle") .attr("class", "node") //radius .attr("r", r) //fill .attr("fill", function(d) { if (d.type === 'metabolite') { return "blue"; } else { return "red"; } }) .call(force.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; }); //applies force per step or 'tick'. force.on("tick", function() { node.attr("cx", function(d) { return dx = Math.max(r, Math.min(width - r, dx)); }) .attr("cy", function(d) { return dy = Math.max(r, Math.min(height - r, dy)); }); 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; }); }); }; for(name_subsystem in graphs) { draw_graphs(name_subsystem); } </script> 

Note. Graphs is the name of the variable in my json file. You need to enable the d3 library.

+2
source

I only work with power layout, with many graphs at the same time.

1 You do not need to have a count variable for each chart.

2 Do not make these variables (force, svg, graph) as an array. That is unnecessary. just declare them above (var svg;) and on. When you call a function, it automatically makes its other copy, and the DOM supports them separately. Therefore, each variable that you use in the chart forces it to be declared on top of the function.

3 You draw all the graphs at the same time, since the new one is called, the previous one stops at creating svg, so only the last graph is built successfully. Therefore, draw them at short intervals.

 <html> <script> function draw_graphs(graphs){ var color = d3.scale.category20(); var force; var div; var svg; var graph; var link; var node; var width = 360; var height = 360; var brush = new Array(); var shiftKey; //loop through the different subsystems in the json-file for(name_subsystem in graphs) { //add a div for each subsystem div = document.createElement("div"); div.style.width = "360px"; div.style.height = "360px"; div.style.cssFloat="left"; div.id = name_subsystem; document.body.appendChild(div); //force is called. all attributes with default values are noted. see API reference on github. force = d3.layout.force() .size([width, height]) .linkDistance(20) .linkStrength(1) .friction(0.9) .charge(-30) .theta(0.8) .gravity(0.1); div.appendChild(document.createTextNode(name_subsystem)); //create the svg rectangle in which other elements can be visualised svg = 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 = svg.append("g") .datum(function() { return {selected: false, previouslySelected: false}; }) .attr("class", "brush"); //force is started force .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.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.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.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.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] <= dx && dx < extent[1][0] && extent[0][1] <= dy && dy < extent[1][1]); }); }) .on("brushend", function() { d3.event.target.clear(); d3.select(this).call(d3.event.target); }) ); //applies force per step or 'tick'. 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 dx; }) .attr("cy", function(d) { return dy; }); }); //with this it works partly //for (var i = 0; i < 5000; ++i)force[count].tick(); }; 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; } } </script> <script> $(document).ready(function() { draw_graphs("pass here the json file"); // this will drawn 2nd graph after 1 second. var t = setTimeout(function(){ draw_graphs("pass here json file"); }, 1000) }); 

+4
source

Source: https://habr.com/ru/post/1496739/


All Articles