Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active January 12, 2022 07:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save mbostock/1136236 to your computer and use it in GitHub Desktop.
Save mbostock/1136236 to your computer and use it in GitHub Desktop.
Spermatozoa
license: gpl-3.0
redirect: https://observablehq.com/@mbostock/tadpoles
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background: #000;
}
ellipse {
fill: #fff;
}
path {
fill: none;
stroke: #fff;
stroke-linecap: round;
}
.mid {
stroke-width: 4px;
}
.tail {
stroke-width: 2px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var n = 100,
m = 12,
degrees = 180 / Math.PI;
var spermatozoa = d3.range(n).map(function() {
var x = Math.random() * width,
y = Math.random() * height;
return {
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1,
path: d3.range(m).map(function() { return [x, y]; }),
count: 0
};
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.selectAll("g")
.data(spermatozoa)
.enter().append("g");
var head = g.append("ellipse")
.attr("rx", 6.5)
.attr("ry", 4);
g.append("path")
.datum(function(d) { return d.path.slice(0, 3); })
.attr("class", "mid");
g.append("path")
.datum(function(d) { return d.path; })
.attr("class", "tail");
var tail = g.selectAll("path");
d3.timer(function() {
for (var i = -1; ++i < n;) {
var spermatozoon = spermatozoa[i],
path = spermatozoon.path,
dx = spermatozoon.vx,
dy = spermatozoon.vy,
x = path[0][0] += dx,
y = path[0][1] += dy,
speed = Math.sqrt(dx * dx + dy * dy),
count = speed * 10,
k1 = -5 - speed / 3;
// Bounce off the walls.
if (x < 0 || x > width) spermatozoon.vx *= -1;
if (y < 0 || y > height) spermatozoon.vy *= -1;
// Swim!
for (var j = 0; ++j < m;) {
var vx = x - path[j][0],
vy = y - path[j][1],
k2 = Math.sin(((spermatozoon.count += count) + j * 3) / 300) / speed;
path[j][0] = (x += dx / speed * k1) - dy * k2;
path[j][1] = (y += dy / speed * k1) + dx * k2;
speed = Math.sqrt((dx = vx) * dx + (dy = vy) * dy);
}
}
head.attr("transform", headTransform);
tail.attr("d", tailPath);
});
function headTransform(d) {
return "translate(" + d.path[0] + ")rotate(" + Math.atan2(d.vy, d.vx) * degrees + ")";
}
function tailPath(d) {
return "M" + d.join("L");
}
</script>
@maelp
Copy link

maelp commented Dec 6, 2012

Nice example, but I'm not sure why you would modify the sinusoid phase at each iteration in k2 = Math.sin(((spermatozoon.count += count) + j * 3) / 300) / speed; rather than simply adding the count to the phase before the loop (spermatozoon.count += count -- note that you should probably add some higher value here like count*10 for more animation), and you don't really have to modify the phase depending on j (simply use k2 = sin(spermatozoon.count/300)/speed )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment