How to draw circles with random sizes around circular path with D3js, so that small circles will be randomly distributed and not overlapping one with each other.
Here is how it should look:
and here is what i was able to get
jQuery(document).ready(function () {
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
data = d3.range(100).map(function () {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
cx : offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
cy : offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
r : rand(size[0], size[1])
};
});
svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r : function (d) {return d.r},
cx : function (d) {return d.cx},
cy : function (d) {return d.cy},
fill : function (d, i) {return color(i % 3)}
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
});
http://jsfiddle.net/yb8bgcrn/1/
UPDATED QUESTION
Is there a way to display it with d3 force layout but without using links ?
I have made some updates to your fiddle and applied collision detection as in the demo I mentioned in the comment. Hope this helps.
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? 0 : -2000;
})
.distance(500)
.size([width, height]);
data = d3.range(100).map(function() {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
x: offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
y: offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
radius: rand(size[0], size[1])
};
});
force
.nodes(data)
.start();
root = data[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
root.px = 250; //Center x
root.py = 275; //Center y
var nodes = svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r: function(d) {
return d.radius
},
cx: function(d) {
return d.x
},
cy: function(d) {
return d.y
},
fill: function(d, i) {
return color(i % 3)
}
});
force.on("tick", function(e) {
var q = d3.geom.quadtree(data),
i = 0,
n = data.length;
while (++i < n) q.visit(collide(data[i]));
svgContainer.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
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;
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT:
Do you mean something like this?
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height).append("g");
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? -20 : -2000;
})
.distance(500)
.size([width, height]);
data = d3.range(100).map(function() {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
x: offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
y: offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
radius: rand(size[0], size[1])
};
});
force
.nodes(data)
.start();
root = data[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
root.px = 250; //Center x
root.py = 275; //Center y
var nodes = svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r: function(d) {
return d.radius
},
cx: function(d) {
return d.x
},
cy: function(d) {
return d.y
},
fill: function(d, i) {
return color(i % 3)
}
});
var rotation = 0;
setInterval(function(){
if(force.alpha()==0){
if(!rotation)
rotation = Math.random() * 50;
else
rotation = rotation+1;
svgContainer.attr("transform","rotate("+rotation+", "+(width/2)+","+(height/2)+")");
}
//force.theta(0.5);
},250);
force.on("tick", function(e) {
var q = d3.geom.quadtree(data),
i = 0,
n = data.length;
while (++i < n) q.visit(collide(data[i]));
svgContainer.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
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;
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
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