Skip to content

Instantly share code, notes, and snippets.

@jkschneider
Created February 7, 2013 16:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkschneider/4732279 to your computer and use it in GitHub Desktop.
Save jkschneider/4732279 to your computer and use it in GitHub Desktop.
D3 Beeswarm Layout

The beeswarm visualization is used to plot highly compact single-dimensional data by allowing data points to be pushed off the principal axis of the data along the normal to that axis. This visualization can be especially useful for time-series data, maintaining the visual ordering of the data points while allowing for localized regions of highly compact data in the view.

Other implementations use force layout, but the force layout simulation naturally tries to reach its equilibrium by pushing data points along both axes, which can be disruptive to the ordering of the data.

This implementation could be improved by replacing the normally distributed random jittering with an intelligent strategy. For my purpose, this sufficed. The total number of iterations over the collision visitor directly affects the probability of collisions in the end state.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Timeline Tests</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script src='js3rdparty/moment.min.js'></script>
<script src='js3rdparty/sockjs-0.2.1.min.js'></script>
<script src='js3rdparty/vertxbus.js'></script>
<style type="text/css">
</style>
</head>
<body>
</body>
<script type="text/javascript">
var data = d3.range(80).map(function(d, i) {
return {
x : (860/80)*i+50,
y : 100,
r : 10
}
})
var color = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.x }))
.range(["steelblue", "brown"])
.interpolate(d3.interpolateHsl)
var norm = d3.random.normal(0, 2.5)
var chart = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 300)
.append("g")
.attr("transform", "translate(10,10)")
var nodes = chart.selectAll("circle.node")
.data(data)
nodes.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x })
.attr("cy", function(d) { return d.y })
.attr("r", function(d) { return d.r })
.attr("fill", function(d) { return color(d.x) })
.style("stroke", "black")
.style("stroke-width", 1.5)
var iterations = 0
while(iterations++ < 100) {
var q = d3.geom.quadtree(data)
for(var i = 0; i < data.length; i++)
q.visit(collide(data[i]))
}
nodes.transition()
.attr("cy", function(d) { return d.y })
function collide(node) {
var r = node.r + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.r + quad.point.r;
if (l < r)
node.y += norm()
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1
}
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment