var width = 1000,
height = 600;
d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 600);
const collideCircle = {
radius: 30,
fill: 'black',
center: {
x: 200,
y: 200
}
};
const radius = 10;
const moreCircles = [...Array(40).keys()].map(i => ({
radius,
fill: 'pink',
center: {
x: 200,
y: 200
}
}));
const simulation = d3.forceSimulation([collideCircle, ...moreCircles])
.force('x', d3.forceX().x(d => d.center.x))
.force('y', d3.forceY().y(d => d.center.y))
.force('collision', d3.forceCollide().radius(d => d.radius))
.on('tick', ticked);
const collideCircles = d3.select('svg')
.append('g')
.selectAll('circle')
.data([collideCircle])
.enter().append('circle')
.attr('fill', d => d.fill)
.attr('r', d => d.radius)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
const moreCirclesGeom = d3.select('svg')
.append('g')
.selectAll('circle')
.data(moreCircles)
.enter().append('circle')
.attr('fill', d => d.fill)
.attr('r', d => d.radius)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
function ticked() {
// stay in the SVG (box) only:
moreCirclesGeom
.attr("cx", function (d) {
return d.x = Math.max(radius, Math.min(width - radius, d.x));
})
.attr("cy", function (d) {
return d.y = Math.max(radius, Math.min(height - radius, d.y));
});
collideCircles
.attr("cx", function (d) {
return d.x = Math.max(radius, Math.min(width - radius, d.x));
})
.attr("cy", function (d) {
return d.y = Math.max(radius, Math.min(height - radius, d.y));
});
}
d3.select('svg').on("mouseover", handleMouseOver);
function handleMouseOver() {
collideCircle.fx = d3.event.x;
collideCircle.fy = d3.event.y;
simulation.alphaTarget(0.1).restart();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>