Skip to content

Instantly share code, notes, and snippets.

@monfera
Last active August 8, 2016 11:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save monfera/9c5efcfa26e202f5653c692a6d3ba7fa to your computer and use it in GitHub Desktop.
Save monfera/9c5efcfa26e202f5653c692a6d3ba7fa to your computer and use it in GitHub Desktop.
Brownian motion with D3 4.0 velocity Verlet physics
license: gpl-3.0

Brownian motion is observable when dust particles (here: red) follow random, erratic paths as they are pushed around by the invisibly small, but fast and numerous gas atoms or molecules. It's not only of interest in natural sciences but is also the keystone in quantitative finance.

It's initially hard to see how the small, fast particles impact the larger dust speckles, but in a minute or so the temperature cools and things slow down.

This Brownian motion example shows physics capabilities of the versatile d3.forceSimulation in D3 4.0 which now uses velocity Verlet integration.

D3 simulated forces are kept to a minimum: besides d3.forceCollide the only other force ensures that particles are not lost in space (perfectly bouncy walls of identical temperature).

A slow cooling is simulated by using a small non-zero value as velocityDecay.

Rendering is done on two superimposed <canvas> layers such that one can be continuously erased while the other shows a historical snail trail as if drawn with marker on a glass plane.

Built with blockbuilder.org

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body style="margin: 0; overflow: hidden">
<canvas id="historical" width="960" height="500" style="position: absolute; left: 0; top: 0; z-index: 1"></canvas>
<canvas id="realtime" width="960" height="500" style="position: absolute; left: 0; top: 0; z-index: 2"></canvas>
<script>
var width = 960
var height = 500
var gasMoleculeCount = 3000
var dustCount = 10
var realtimeContext = document.querySelector('#realtime')
.getContext('2d', {alpha: true})
var historicalContext = document.querySelector('#historical')
.getContext('2d', {alpha: true})
realtimeContext.transform(1, 0, 0, 1, width / 2, height / 2)
historicalContext.transform(1, 0, 0, 1, width / 2, height / 2)
var radius = function(node) { return node.radius }
var gasMolecules = d3.range(gasMoleculeCount).map(function(d) {
var randomAngle = Math.random() * 2 * Math.PI
return {
radius: 0.5,
x: Math.random() * width - width / 2,
y: Math.random() * height - height / 2,
vx: 5 * Math.cos(randomAngle),
vy: 5 * Math.sin(randomAngle)
}
})
var dustParticles = d3.range(dustCount).map(function(d) {
return {
radius: 3,
x: (Math.random() * width - width / 2) / 2,
y: (Math.random() * height - height / 2) / 2,
vx: 0,
vy: 0
}})
var nodes = dustParticles.concat(gasMolecules)
var recycle = function(alpha) {
var node
var i
for (i = 0; i < nodes.length; i++) {
node = nodes[i]
if(Math.abs(node.x) > width / 2 - node.radius) {
node.vx *= -1
}
if(Math.abs(node.y) > height / 2 - node.radius) {
node.vy *= -1
}
}
}
d3.forceSimulation(nodes)
.alphaDecay(0)
.velocityDecay(5e-4)
.force("collide", d3.forceCollide().radius(radius).strength(1).iterations(1))
.force("recycle", recycle)
.on("tick", render)
realtimeContext.lineWidth = 1
realtimeContext.fillStyle = "red"
historicalContext.lineWidth = 5
historicalContext.strokeStyle = "rgba(128, 0, 255, 0.02)"
function render() {
var i
var particle
realtimeContext.clearRect(-width / 2, -height / 2, width, height)
historicalContext.beginPath()
for(i = 0; i < dustCount; i++) {
particle = dustParticles[i]
historicalContext.moveTo(particle.x, particle.y)
historicalContext.lineTo(particle.x + 1, particle.y + 1)
}
historicalContext.stroke()
realtimeContext.beginPath()
for(i = 0; i < gasMoleculeCount; i++) {
particle = gasMolecules[i]
realtimeContext.moveTo(particle.x, particle.y)
realtimeContext.lineTo(particle.x + 1, particle.y)
}
realtimeContext.stroke()
realtimeContext.beginPath()
for(i = 0; i < dustCount; i++) {
particle = dustParticles[i]
var r = particle.radius
realtimeContext.moveTo(particle.x + r, particle.y)
realtimeContext.arc(particle.x, particle.y, r, 0, 2 * Math.PI)
}
realtimeContext.fill()
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment