Skip to content

Instantly share code, notes, and snippets.

@ctlusto
Last active October 28, 2015 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ctlusto/be617d54d5be054151c6 to your computer and use it in GitHub Desktop.
Save ctlusto/be617d54d5be054151c6 to your computer and use it in GitHub Desktop.
Seeing Spots
<!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>
// 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();
});
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