Skip to content Skip to sidebar Skip to footer

Multiple Force-layout Graphs With D3 In Seperate Svg/div's

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

Solution 1:

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.

<html><script>functiondraw_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 = newArray();
var shiftKey;


//loop through the different subsystems in the json-filefor(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) { returnMath.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",functiondragstart(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] <= 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.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();
};

functionkeydown() {
if (!d3.event.metaKey) switch (d3.event.keyCode) {
case38: nudge( 0, -1); break; // UPcase40: nudge( 0, +1); break; // DOWNcase37: nudge(-1,  0); break; // LEFTcase39: nudge(+1,  0); break; // RIGHT
}
shiftKey = d3.event.shiftKey || d3.event.metaKey;
}

functionkeyup() {
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)

});

Solution 2:

Here the code I finally used with the help of the comments above, maybe helpful for others as well:

<scripttype="text/javascript"src="d3_splitted_var.json"></script><script>functiondraw_graphs(name_subsystem){   

    var force;
    var div;
    var svg;
    var link;
    var node;
    var width = 360;
    var height = 360;
    var r=5;
    var brush = newArray();
    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) { returnMath.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",functiondragstart(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 d.x = Math.max(r, Math.min(width - r, d.x)); })
            .attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, 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; });
    });
};
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 include the d3-library.

Post a Comment for "Multiple Force-layout Graphs With D3 In Seperate Svg/div's"