An animation demonstrating Steven Strogatz's colliding dogs problem.
Last active
October 28, 2015 23:55
-
-
Save ctlusto/be617d54d5be054151c6 to your computer and use it in GitHub Desktop.
Seeing Spots
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Seeing Spots</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<p>Number of Dogs: <select id="number"> | |
<option value="3">3</option> | |
<option value="4">4</option> | |
<option value="5">5</option> | |
<option value="6">6</option> | |
<option value="7" selected>7</option> | |
<option value="8">8</option> | |
<option value="9">9</option> | |
<option value="10">10</option> | |
<option value="11">11</option> | |
<option value="12">12</option> | |
</select> | |
</p> | |
<p>Speed: <select id="speed"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
<option value="4">4</option> | |
<option value="5" selected>5</option> | |
<option value="6">6</option> | |
<option value="7">7</option> | |
<option value="8">8</option> | |
<option value="9">9</option> | |
<option value="10">10</option> | |
</select> | |
</p> | |
<p> | |
<button id="run">Run, Spots, Run!</button> | |
<button id="reset">Reset</button> | |
</p> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
<script src="main.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Dimensions and constants | |
var w = 900, | |
h = 400, | |
r = h / 2.5, | |
dogSize = 5, | |
TAU = 2 * Math.PI; | |
// Trig aliases | |
var cos = Math.cos | |
sin = Math.sin; | |
// All dogs go to heaven | |
var center = {x: w / 2, y: h / 2}; | |
// Set up the D3 playground | |
var svg = d3.select('body').append('svg') | |
.attr('width', w) | |
.attr('height', h); | |
// Fence it in | |
svg.append('circle') | |
.attr('class', 'pen') | |
.attr('cx', center.x) | |
.attr('cy', center.y) | |
.attr('r', r); | |
// Should the dogs be running? | |
var running = false; | |
// Give the dogs room to run | |
function Yard(numDogs, speed) { | |
this.numDogs = numDogs; | |
this.speed = speed; | |
this.dogs = []; | |
this.makeDogs(); | |
} | |
// Let there be dogs | |
Yard.prototype.makeDogs = function() { | |
var numDogs = this.numDogs; | |
var delta = TAU / numDogs; | |
while (numDogs--) { | |
var theta = delta * numDogs; | |
this.dogs.push(new Dog({ | |
x: center.x + r * cos(theta), | |
y: center.y + r * sin(theta) | |
}, this.speed)); | |
} | |
}; | |
// Get a unit vector describing each dog's next step | |
Yard.prototype.getDirections = function() { | |
this.dogs.forEach(function(dog, idx, arr) { | |
var targetDog = arr[idx + 1] || arr[0]; | |
var directionVector = { | |
x: targetDog.pos.x - dog.pos.x, | |
y: targetDog.pos.y - dog.pos.y | |
}; | |
var distance = hypot(directionVector.x, directionVector.y); | |
var normedDirectionVector = { | |
x: directionVector.x / distance, | |
y: directionVector.y / distance | |
}; | |
dog.run(normedDirectionVector); | |
}); | |
}; | |
// Exercise the dogs | |
Yard.prototype.updatePositions = function() { | |
var spots = svg.selectAll('.dog').data(this.dogs); | |
spots | |
.attr('cx', function(d) { return d.pos.x; }) | |
.attr('cy', function(d) { return d.pos.y }); | |
spots.enter().append('circle') | |
.attr('class', 'dog') | |
.attr('cx', function(d) { return d.pos.x; }) | |
.attr('cy', function(d) { return d.pos.y }) | |
.attr('r', dogSize); | |
spots.exit().remove(); | |
}; | |
// Reset the yard | |
Yard.prototype.reset = function() { | |
running = false; | |
this.dogs = []; | |
this.makeDogs(); | |
}; | |
// (Irish?) setter for the speed | |
Yard.prototype.setSpeed = function(newSpeed) { | |
this.speed = newSpeed; | |
this.reset(); | |
}; | |
// (English?) setter for the number of dogs | |
Yard.prototype.setNumber = function(n) { | |
this.numDogs = n; | |
this.reset(); | |
}; | |
// Dog recipe | |
function Dog(pos, speed) { | |
this.pos = pos; | |
this.speed = speed; | |
} | |
// Run, spot, run | |
Dog.prototype.run = function(vector) { | |
this.pos.x += vector.x * this.speed; | |
this.pos.y += vector.y * this.speed; | |
}; | |
// Avoid [preposition]-flow errors in distance calculations | |
// https://en.wikipedia.org/wiki/Hypot | |
function hypot(x, y) { | |
var t; | |
x = Math.abs(x); | |
y = Math.abs(y); | |
t = Math.min(x, y); | |
x = Math.max(x, y); | |
t /= x; | |
return x * Math.sqrt(1 + t * t); | |
} | |
// Let the dogs out | |
var yard = new Yard(7, 5); | |
var tick = function() { | |
if (running) { | |
yard.getDirections(); | |
yard.updatePositions(); | |
} | |
}; | |
yard.updatePositions(); | |
d3.timer(tick); | |
// Dog handlers...get it? | |
d3.select('#number').on('change', function() { | |
yard.setNumber(this.value); | |
yard.updatePositions(); | |
}); | |
d3.select('#speed').on('change', function() { | |
yard.setSpeed(this.value); | |
yard.updatePositions(); | |
}); | |
d3.select('#run').on('click', function() { | |
running = true; | |
}); | |
d3.select('#reset').on('click', function() { | |
yard.reset(); | |
yard.updatePositions(); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font: 0.8em "Open Sans", sans-serif; | |
padding: 20px; | |
} | |
svg { | |
margin-top: -100px; | |
pointer-events: none; | |
} | |
.dog { | |
fill: #4f81bd; | |
} | |
.pen { | |
stroke: #ddd; | |
fill: none; | |
stroke-width: 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment