Reputation: 13
I'm new with d3.js, what I'm trying to do is close to this : http://codepen.io/fabiobiondi/pen/nFxyD
// GROUPS: 0 Web | 1: Adobe | 2: hybrid
var data = [
{"id": 0, "name": "AngularJS", "r": 50 },
{"id": 0, "name": "HTML5", "r": 40 },
{"id": 0, "name": "Javascript", "r": 30 },
{"id": 0, "name": "NodeJs", "r": 30 },
{"id": 0, "name": "D3.js", "r": 40 },
{"id": 0, "name": "CreateJS", "r": 45 },
{"id": 0, "name": "Cordova", "r": 40 },
{"id": 0, "name": "CSS", "r": 40 },
{"id": 0, "name": "SVG", "r": 20 },
{"id": 0, "name": "PHP", "r": 20 },
{"id": 0, "name": "jQuery", "r": 30 },
{"id": 1, "name": "Actionscript", "r": 50 },
{"id": 1, "name": "Flash", "r": 32 },
{"id": 1, "name": "Flex", "r": 50 },
{"id": 1, "name": "AIR", "r": 40 },
{"id": 1, "name": "Photoshop", "r": 30 },
{"id": 1, "name": "Illustrator", "r": 30 },
{"id": 2, "name": "Node Webkit", "r": 40 },
{"id": 2, "name": "Chrome App", "r": 30 },
{"id": 2, "name": "Cordova", "r": 45 },
];
var width = window.innerWidth,
height = 450;
var fill = d3.scale.category10();
var nodes = [], labels = [],
foci = [{x: 0, y: 150}, {x: 350, y: 150}, {x: 200, y: 150}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
//.attr("domflag", '');
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-400)
//.chargeDistance(200)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
//var node = svg.selectAll("circle");
var node = svg.selectAll("g");
var counter = 0;
function tick(e) {
var k = .1 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function resize() {
width = window.innerWidth;
force.size([width, height]);
force.start();
}
d3.select(window).on('resize', resize);
What I don't understand is how can I control the way the circles can or can't collide.
I have tried collide function like this https://bl.ocks.org/mbostock/1804919 but I don't get how it works. When I change padding value, circles keep avoiding themselves. What I would like is to allow collide but not too much (let's say 10px), to be sure that the text inside is never hidden.
Upvotes: 1
Views: 1095
Reputation: 10612
I have implemented this example : https://bl.ocks.org/mbostock/7881887
I added the collision function :
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
//console.log(d)
var r = d.r + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.r + quad.point.r + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
This is all explained in the link above so I wont need to explain here.
In the example its d.radius but you have d.r.Also the maxRadius which I worked out like this :
var maxRadius = data.reduce(function(sum, d){
return Math.max(sum,d.r)
}, 0); //get maximum radius
The above reduces the data array and gets the biggest value of r (radius). They are the only changes I had made.
And I have updated your tick function :
function tick(e) {
var k = .1 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
// console.log(o)
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node
.each(collide(.5)) //call collide function here
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
This takes care of the multi foci you had and the collision detection.
Updated fiddle : https://jsfiddle.net/thatOneGuy/392kwuru/
Upvotes: 1