I'm trying to force nodes into different clusters in force layout based on a certain attribute in the data like "group." I'm adapting the code from Mike Bostock's multi foci force layout example (code, example) and I've been successful in adding in my own data but I haven't been able to specify how many clusters there are and how to assign a node to a cluster.
I'm relatively new to d3 and JavaScript and I haven't been able to find many examples of multi foci applications. Here's my d3 code, any help or input is appreciated:
var width = 960,
height = 500;
var fill = d3.scale.category10();
d3.json("test.json" , function(error, json){
var root = json.nodes[0];
root.radius = 0;
root.fixed = true;
var force = d3.layout.force()
.nodes(json.nodes)
.size([width, height])
.gravity(0.06)
.charge(function(d, i) { return i ? 0 : -2000; })
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var elem = svg.selectAll(".elem")
.data(json.nodes)
.enter()
.append("g")
.attr("class", "elem");
elem.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 40)
.style("fill", function(d, i) { return fill(i & 3); })
.style("stroke", function(d, i) { return d3.rgb(fill(i & 3)).darker(2); })
.call(force.drag)
.on("mousedown", function() { d3.event.stopPropagation(); });
elem.append("text")
.text(function(d){ return d.name; });
svg.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);
d3.select("body")
.on("mousedown", mousedown);
I've specifically been trying to figure out how this tick function is working. I did some research and found that the "&" is a bitwise operator and I noticed that changing the number after it is what is changing how many clusters there are and which nodes are in each. But preferably I would like to be able to point to something like d.group here to specify the cluster.
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
json.nodes.forEach(function(o, i) {
o.y += i & 3 ? k : -k;
o.x += i & 2 ? k : -k;
});
var q = d3.geom.quadtree(json.nodes),
i = 0,
n = json.nodes.length;
while (++i < n) q.visit(collide(json.nodes[i]));
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
function collide(node) {
var r = node.radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
}
svg.on("mousemove", function() {
var p1 = d3.mouse(this);
root.px = p1[0];
root.py = p1[1];
force.resume();
});
function mousedown() {
json.nodes.forEach(function(o, i) {
o.x += (Math.random() - .5) * 40;
o.y += (Math.random() - .5) * 40;
});
force.resume();
}
});
*Note that I have also implemented collision detection to repel nodes but I don't think this is affecting the clusters at the moment.
My data is currently stored in a json file called test.json:
{
"nodes":[
{
"name": "Null",
"radius": 40,
"color": "#ff0000",
"gravity": 0.05,
"group": 1
},
{
"name": "One",
"radius": 40,
"color": "#ffff00",
"gravity": 0.05,
"group": 1
},
{
"name": "Two",
"radius": 40,
"color": "#33cc33",
"gravity": 0.2,
"group": 1
},
{
"name": "Three",
"radius": 40,
"color": "#3399ff",
"gravity": 0.9,
"group": 1
},
{
"name": "Four",
"radius": 40,
"color": "#ffff00",
"gravity": 0.05,
"group": 6
},
{
"name": "Five",
"radius": 40,
"color": "#33cc33",
"gravity": 0.2,
"group": 6
},
{
"name": "Six",
"radius": 40,
"color": "#3399ff",
"gravity": 0.9,
"group": 6
}
]
}