I am just starting on D3, so if anyone has any general suggestions on thing I might not be doing correctly/optimally, please let me know :)
I am trying to create a Force Directed graph with the nodes spaced out evenly (or close enough) around the center root node (noted by the larger size).
Here's an example of the layout I'm trying to achieve (I understand it won't be the same every time):
I have the following graph:
var width = $("#theVizness").width(),
height = $("#theVizness").height();
var color = d3.scale.ordinal().range(["#ff0000", "#fff000", "#ff4900"]);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("#theVizness").append("svg")
.attr("width", width)
.attr("height", height);
var loading = svg.append("text")
.attr("class", "loading")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Loading...");
/*
ForceDirectData.json
{
"nodes":[
{"name":"File1.exe","colorGroup":0},
{"name":"File2.exe","colorGroup":0},
{"name":"File3.exe","colorGroup":0},
{"name":"File4.exe","colorGroup":0},
{"name":"File5.exe","colorGroup":0},
{"name":"File6.exe","colorGroup":0},
{"name":"File7.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File9.exe","colorGroup":0}
],
"links":[
{"source":1,"target":0,"value":10},
{"source":2,"target":0,"value":35},
{"source":3,"target":0,"value":50},
{"source":4,"target":0,"value":50},
{"source":5,"target":0,"value":65},
{"source":6,"target":0,"value":65},
{"source":7,"target":0,"value":81},
{"source":8,"target":0,"value":98},
{"source":9,"target":0,"value":100}
]
}
*/
d3.json("https://dl.dropboxusercontent.com/u/5772230/ForceDirectData.json", function (error, json) {
var nodes = json.nodes;
force.nodes(nodes)
.links(json.links)
.linkDistance(function (d) {
return d.value * 1.5;
})
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 0;
return charge;
})
.friction(0.4);
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
var files = svg.selectAll(".file")
.data(json.nodes)
.enter().append("circle")
.attr("class", "file")
.attr("r", 10)
.attr("fill", function (d) {
return color(d.colorGroup);
});
var totalNodes = files[0].length;
files.append("title")
.text(function (d) { return d.name; });
force.start();
for (var i = totalNodes * totalNodes; i > 0; --i) force.tick();
nodes[0].x = width / 2;
nodes[0].y = height / 2;
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; });
files.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("class", function(d){
var classString = "file"
if (d.index === 0) classString += " rootFile";
return classString;
})
.attr("r", function(d){
var radius = 10;
if (d.index === 0) radius = radius * 2;
return radius;
});
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; });
files.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
loading.remove();
});
I have already tried getting close to this with the charge()
method. I thought giving every node besides the root node a higher charge would accomplish this, but it did not.
Yes, force layout is a perfect tool for situations like yours.
You just need to change a little initialization of the layout, like this
force.nodes(nodes)
.links(json.links)
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 10 * charge;
return charge;
});
and voila
Explanation. I had to remove settings for friction
and linkDistance
since they affected placement in a bad way. The charge
for root node is 10 times larger so that all other nodes are dominantly pushed away from the root. Other nodes also repel each other, and the perfect symmetry is achieved at the end, as a result.
Jsfiddle is here.
I see from your code that you attempted to affect distance from the root node and other nodes by utilizing linkDistance
that is dependant on data. However, it might be better (although counter-intuitive) to use linkStrength
for that purpose, like this
force.nodes(nodes)
.links(json.links)
.linkStrength(function (d) {
return d.value / 100.0;
})
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 10 * charge;
return charge;
});
but you need to experiment.
For centering and fixing the root node, you can use this
nodes[0].fixed = true;
nodes[0].x = width / 2;
nodes[0].y = height / 2;
but before initialization of layout, like in this Jsfiddle.