r = d.rt + 10
to this r = d.rt + rmax
in the collision function to tighten up the control on overlapsPure d3 version
Attempt at CSS version
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<title>https://stackoverflow.com/questions/32521887/animate-objects-in-force-layout-in-d3-js/32523428#32523428</title>
<link rel="stylesheet" type="text/css" href="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.css">
<style>
body {
background: black;
margin:0;
padding:0;
}
#histogram rect{
-webkit-transition: all 0.1s linear;
-moz-transition: all 0.1s linear;
-o-transition: all 0.1s linear;
transition: all 0.1s linear;
}
#bubble-cloud {
background: url("https://dummyimage.com/100x100/111/333?text=sample") 0 0;
width: 960px;
height: 470px;
/*overflow: hidden;*/
position: relative;
margin:0 auto;
}
svg {
outline: rgba(242,216,28,1);
overflow: visible;
}
</style>
</head>
<body>
<div id="bubble-cloud"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/elapsedtime/elapsed-time-2.0.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/plot-transform.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/fps-histogram.js"></script>
<script>
// helpers
var random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
},
metrics = d3.select('#bubble-cloud').append("div")
.attr("id", "metrics")
.style("white-space", "pre"),
elapsedTime = outputs.ElapsedTime("#metrics", {
border: 0, margin: 0, "box-sizing": "border-box",
padding: "0 0 0 6px", background: "black", "color": "orange"
})
.message(function(value) {
var this_lap = this.lap().lastLap, aveLap = this.aveLap(this_lap)
return 'alpha:' + d3.format(" >7,.3f")(value)
+ '\tframe rate:' + d3.format(" >4,.1f")(1 / aveLap) + " fps"
}),
hist = d3.ui.FpsMeter("#metrics", {display: "inline-block"}, {
height: 10, width: 100,
values: function(d){return 1/d},
domain: [0, 60]
}),
// mock data
colors = [
{
fill: 'rgba(242,216,28,0.3)',
stroke: 'rgba(242,216,28,1)'
},
{
fill: 'rgba(207,203,196,0.3)',
stroke: 'rgba(207,203,196,1)'
},
{
fill: 'rgba(0,0,0,0.2)',
stroke: 'rgba(100,100,100,1)'
}
];
// initialize
var container = d3.select('#bubble-cloud');
var containerWidth = 960;
var containerHeight = 470 - elapsedTime.selection.node().clientHeight;
var svgContainer = container
.append('svg')
.attr('width', containerWidth)
.attr('height', containerHeight);
var data = [],
rmin = 30,
rmax = 60;
d3.range(0, 3).forEach(function(j){
d3.range(0, 8).forEach(function(i){
var r = random(rmin, rmax);
data.push({
text: 'text' + i,
category: 'category' + j,
x: random(rmax, containerWidth - rmax),
y: random(rmax, containerHeight - rmax),
r: r,
fill: colors[j].fill,
stroke: colors[j].stroke,
get v() {
var d = this;
return {x: d.x - d.px || 0, y: d.y - d.py || 0}
},
set v(v) {
var d = this;
d.px = d.x - v.x;
d.py = d.y - v.y;
},
get s() {
var v = this.v;
return Math.sqrt(v.x * v.x + v.y * v.y)
},
set s(s1){
var s0 = this.s, v0 = this.v;
if(!v0 || s0 == 0) {
var theta = Math.random() * Math.PI * 2;
this.v = {x: Math.cos(theta) * s1, y: Math.sin(theta) * s1}
} else this.v = {x: v0.x * s1/s0, y: v0.y * s1/s0};
},
set sx(s) {
this.v = {x: s, y: this.v.y}
},
set sy(s) {
this.v = {y: s, x: this.v.x}
},
});
})
});
// collision detection
// derived from https://bl.ocks.org/mbostock/1748247
function collide(alpha, s0) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var drt = d.rt;
boundaries(d, drt);
var r = drt + rmax,
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 = drt + quad.point.rt;
if (l < r) {
l = (l - r) / l * (1 + 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;
});
};
function boundaries(d, _drt) {
var moreThan, v0,
drt = _drt || d.rt;
// boundaries
//reflect off the edges of the container
// check for boundary collisions and reverse velocity if necessary
if((moreThan = d.x > (containerWidth - drt)) || d.x < drt) {
d.escaped |= 2;
// if the object is outside the boundaries
// manage the sign of its x velocity component to ensure it is moving back into the bounds
if(~~d.v.x) d.sx = d.v.x * (moreThan && d.v.x > 0 || !moreThan && d.v.x < 0 ? -1 : 1);
// if vx is too small, then steer it back in
else d.sx = (~~Math.abs(d.v.y) || Math.min(s0, 1)*2) * (moreThan ? -1 : 1);
// clear the boundary without affecting the velocity
v0 = d.v;
d.x = moreThan ? containerWidth - drt : drt;
d.v = v0;
// add a bit of hysteresis to quench limit cycles
} else if (d.x < (containerWidth - 2*drt) && d.x > 2*drt) d.escaped &= ~2;
if((moreThan = d.y > (containerHeight - drt)) || d.y < drt) {
d.escaped |= 4;
if(~~d.v.y) d.sy = d.v.y * (moreThan && d.v.y > 0 || !moreThan && d.v.y < 0 ? -1 : 1);
else d.sy = (~~Math.abs(d.v.x) || Math.min(s0, 1)*2) * (moreThan ? -1 : 1);
v0 = d.v;
d.y = moreThan ? containerHeight - drt : drt;
d.v = v0;
} else if (d.y < (containerHeight - 2*drt) && d.y > 2*drt) d.escaped &= ~4;
}
}
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0.001)
.charge(100)
.friction(.8)
.on("start", function() {
elapsedTime.start(100);
});
// load data
force.nodes(data)
.start();
// create item groups
var node = svgContainer.selectAll('.node')
.data(data)
.enter()
.append('g')
.attr('class', 'node')
.call(force.drag);
// create circles
var circles = node.append('circle')
.classed('circle', true)
.attr('r', function (d) {
return d.r;
})
.style('fill', function (d) {
return d.fill;
})
.style('stroke', function (d) {
return d.stroke;
})
.each(function(d){
// add dynamic r getter
var n= d3.select(this);
Object.defineProperty(d, "rt", {get: function(){
return +(n.attr("r").replace("px", ""))
}})
});
// create labels
node.append('text')
.text(function(d) {
return d.text
})
.classed('text', true)
.style({
'fill': '#ffffff',
'text-anchor': 'middle',
'font-size': '10px',
'font-weight': 'bold',
'text-transform': 'uppercase',
'font-family': 'Tahoma, Arial, sans-serif'
})
.attr('x', function (d) {
return 0;
})
.attr('y', function (d) {
return - rmax/5;
});
node.append('text')
.text(function(d) {
return d.category
})
.classed('category', true)
.style({
'fill': '#ffffff',
'font-family': 'Tahoma, Arial, sans-serif',
'text-anchor': 'middle',
'font-size': '8px'
})
.attr('x', function (d) {
return 0;
})
.attr('y', function (d) {
return rmax/4;
});
var lines = node.append('line')
.classed('line', true)
.attr({
x1: function (d) {
return - d.r + rmax/10;
},
y1: function (d) {
return 0;
},
x2: function (d) {
return d.r - rmax/10;
},
y2: function (d) {
return 0;
}
})
.attr('stroke-width', 1)
.attr('stroke', function (d) {
return d.stroke;
})
.each(function(d){
// add dynamic x getter
var n= d3.select(this);
Object.defineProperty(d, "lxt", {get: function(){
return {x1: +n.attr("x1").replace("px", ""), x2: +n.attr("x2").replace("px", "")}
}})
});
// put circle into movement
force.on('tick', function t(e){
var s0 = 0.25, k = 0.3;
a = e.alpha ? e.alpha : force.alpha();
elapsedTime.mark(a);
if(elapsedTime.aveLap.history.length)
hist(elapsedTime.aveLap.history);
for ( var i = 0; i < 2; i++) {
circles
.each(collide(a, s0));
}
// regulate the speed of the circles
data.forEach(function reg(d){
if(!d.escaped) d.s = (s0 - d.s * k) / (1 - k);
});
// var f = d3.format("> 8.3%"), dx1plus= 0, dx1neg = 0, dx2plus= 0, dx2neg = 0,
// max = Math.max, min = Math.min;
// lines.each(function(d, i){
// console.log(Array(i).join("\t") + d.rt)
// });
node.attr("transform", function position(d){return "translate(" + [d.x, d.y] + ")"});
force.alpha(0.05);
});
// animate
window.setInterval(function(){
var tinfl = 3000, tdefl = 1000, inflate = [200, 10], deflate = "easeOutCubic";
for(var i = 0; i < data.length; i++) {
if(Math.random()>0.8) data[i].r = random(rmin,rmax);
}
circles.filter(function(d){return !d.scheduled && d.r != d.rt})
.each(function(d) {
// console.log("\ton circle " + d.category + " " + d.text)
var delta = d.r - d.rt, defl = delta < 0;
if(~~delta) $(this).velocity(
{r: d.r},
{
duration: defl ? tdefl : tinfl,
easing: defl ? deflate : inflate,
begin: transFlag("start", d),
complete: transFlag("end", d)
});
});
// $("svg .node circle").velocity({r: function(){
// return d3.select(this).datum().r
// }}, tinfl, inflate)
lines.filter(function(d){return !d.scheduled && d.r != d.rt})
.each(function(d) {
// console.log("\ton line " + d.category + " " + d.text)
var delta = d.r - d.rt, defl = delta < 0;
if(~~delta)
$(this).velocity({
x1: -d.r + rmax / 10,
x2: d.r - rmax / 10
}, defl ? tdefl : tinfl, defl ? deflate : inflate)
});
// $("svg .node line").velocity({
// x1: function(){return -d3.select(this).datum().r + rmax / 10},
// x2: function(){return d3.select(this).datum().r - rmax / 10},
// }, tinfl, inflate);
function transFlag(event, d){
return {
start: function(){
window.setTimeout(function() {
d.scheduled = true;
// console.log("\t\tstart " + d.category + " " + d.text)
}, 0);
},
end: function(){
window.setTimeout(function(){
d.scheduled = false;
// console.log("\t\tend " + d.category + " " + d.text)
}, tinfl);
}
}[event]
}
}, 2 * 500);
</script>
</body>
</html>
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-2.0.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/elapsedtime/elapsed-time-2.0.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/plot-transform.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/fps-histogram.js
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js
https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.ui.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-2.0.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.js