I have seen solutions for creating multiple force layouts on a single page, where each layout is contained in its own SVG; however, I have been unable to find help on how to include multiple force layouts within a single SVG. Each layout has its own data associated with it.
An example of what I am currently doing can be found at http://jsfiddle.net/connorgr/SRAJa/. I have included the key part of the code below. The end result looks an awful lot like the force layout was never activated (or deleted) for all but the last node/link data. Is there any way to prevent this from happening?
I am unable to merge the data together and use only one layout because of the use case for the visualization I'm building.
/**
* Creates a force layout in svgObj for each element in graphs
* (svg) svgObj - The SVG to include the force layouts in
* (json) graphs - An array of {"nodes":[...],"links":[...]} objects
*/
function generateMultiForce(svgObj, graphs) {
for(var i=0; i < graphs.length; i++) {
var graph = graphs[i];
var graphArea = svgObj.append("g");
var force = d3.layout.force()
.charge(-200)
.linkDistance(45)
.size([width, height])
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = graphArea.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var nodeGroup = graphArea.selectAll("g")
.data(graph.nodes)
.enter().append("g")
.call(force.drag);
var node = nodeGroup.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group); });
var text = nodeGroup.append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
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; });
nodeGroup.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")";
});
});
}
}
The following approach restricts force, node, and link data to their corresponding force-directed layouts. In this way, you can add as many layouts as you want to the same SVG without causing inter-node interference. Each layout may be formatted individually. If you do end up wanting the layouts to influence one another, you can edit their respective tick functions.
function layout1(inputNodes, inputLinks) {
var force = d3.layout.force();
var nodes = force.nodes();
var links = force.links();
var update = function() {
//append nodes and links from data
force.on("tick",function(e){
//tick movement
}
}
for(var i=0; i<inputNodes.length; i++){
nodes.push(inputNodes[i]);
}
for(var i=0; i<inputLinks.length; i++){
links.push(inputLinks[i]);
}
update();
}
Now the second force-directed layout can have an identical structure, and the same variable names as well:
function layout2(inputNodes, inputLinks) {
var force = d3.layout.force();
var nodes = force.nodes();
var links = force.links();
var update = function() {
//append nodes and links from data
force.on("tick",function(e){
//tick movement
}
}
for(var i=0; i<inputNodes.length; i++){
nodes.push(inputNodes[i]);
}
for(var i=0; i<inputLinks.length; i++){
links.push(inputLinks[i]);
}
update();
}
Finally instantiate with whatever data:
var layout1 = new layout1(inputNodes, inputLinks);
var layout2 = new layout2(inputNodes, inputLinks);
This method can be adopted to creating multiple layouts on the fly. Hope that is close to what you're looking for.
You can write the generateMultiForce function as a jquery plugin - this seems to keep the graphs independent and apply force layout to both:
var width = 600,
height = 600;
var color = d3.scale.category20();
var data = [
{
"nodes": [
{"name": "Hello"},
{"name": "World"}
],
"links": [
{"source": 0, "target": 1, "value": 0.5}
]
},
{
"nodes": [
{"name": "Zero"},
{"name": "One"},
{"name": "Two"}
],
"links": [
{"source": 0, "target": 1, "value": 0.25},
{"source": 1, "target": 2, "value": 0.5},
{"source": 2, "target": 0, "value": 0.25}
]
}
];
(function( $ ) {
$.fn.generateMultiForce = function(svgObj) {
return this.each(function() {
var graph = this;
var graphArea = svgObj.append("g");
var force = d3.layout.force()
.charge(-200)
.linkDistance(45)
.size([width, height])
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = graphArea.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var nodeGroup = graphArea.selectAll("g")
.data(graph.nodes)
.enter().append("g")
.call(force.drag);
var node = nodeGroup.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group); });
var text = nodeGroup.append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
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; });
nodeGroup.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")";
});
});
});
};
})(jQuery);
var svgTest = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
$(data).generateMultiForce(svgTest);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With