Skip to content

Instantly share code, notes, and snippets.

@d2fn
Last active August 29, 2015 14:03
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 d2fn/311928528b14ae531063 to your computer and use it in GitHub Desktop.
Save d2fn/311928528b14ae531063 to your computer and use it in GitHub Desktop.
Dendrite Growth - nearest neighbor search using d3 quadtree

Dendrite growth using d3 quadchart nearest neighbor search.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background: rgb(255, 255, 255);
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
maxRadius = 8;
var makePoint = pointBuilder(maxRadius, width, height);
var svg = d3.select("body")
.append("svg")
.attr({width: width, height: height});
var seedPoints = [
[width/2, height/2, 3]
];
var sample = findClosest(seedPoints, width, height, 1000);
var circles = svg.append("g");
var lines = svg.append("g");
circles.selectAll("circle")
.data(seedPoints)
.enter()
.append("circle")
.attr({
cx: function(d, i) { return d[0]; },
cy: function(d, i) { return d[1]; },
r: function(d, i) { return d[2]; },
fill: "rgb(0, 0, 0)",
opacity: 0
})
d3.timer(function() {
for(var i = 0; i < 7; i++) {
var p = makePoint();
var s = sample(p);
if(!s) return true;
var nearest = s[0];
var moveTo = s[1];
circles.append("circle")
.attr({
r: 0,
cx: nearest[0],
cy: nearest[1],
fill: "rgb(0, 0, 0)",
opacity: 0
})
.transition()
.attr({
r: moveTo[2],
cx: moveTo[0],
cy: moveTo[1],
opacity: 1
});
lines.append("line")
.attr({
stroke: "rgba(255, 0, 0, 1)",
x1: nearest[0],
y1: nearest[1],
x2: nearest[0],
y2: nearest[1]
})
.transition()
.attr({
x1: nearest[0],
y1: nearest[1],
x2: moveTo[0],
y2: moveTo[1]
});
}
});
function findClosest(seedPoints, width, height, maxSamples) {
var samples = 0;
var quadtree = d3.geom.quadtree()
.extent([[-1, -1], [width+1, height+1]])(seedPoints);
var points = seedPoints;
return function(d) {
var x = d[0],
y = d[1],
r = d[2];
if(++samples > maxSamples) return;
var nearest, minDistance = Math.sqrt(width*width + height*height);
quadtree.visit(function(node, x1, y1, x2, y2) {
var p = node.point;
if(p) {
var d = dist(p[0], p[1], x, y);
if(d < minDistance) {
nearest = p;
minDistance = d;
}
else {
return true;
}
}
});
var dx = x - nearest[0];
var dy = y - nearest[1];
var t = Math.atan2(dy, dx);
var moveToX = nearest[0] + (r + nearest[2]) * Math.cos(t);
var moveToY = nearest[1] + (r + nearest[2]) * Math.sin(t);
var moveTo = [moveToX, moveToY, r];
quadtree.add(moveTo);
points.push(moveTo);
return [nearest, moveTo];
};
}
function pointBuilder(maxRadius, width, height) {
return function() {
return [
Math.random() * width,
Math.random() * height,
1 + Math.random() * (maxRadius-1)
];
};
}
// calculate euclidean distance of two points with coordinates: a(ax, ay) and b(bx, by)
function dist(ax, ay, bx, by){
return Math.sqrt(Math.pow(ax-bx, 2) + Math.pow(ay-by, 2));
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment