Skip to content

Instantly share code, notes, and snippets.

@w8r
Forked from mbostock/.block
Last active August 29, 2015 14:23
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 w8r/6aa0d2c548178757fd1a to your computer and use it in GitHub Desktop.
Save w8r/6aa0d2c548178757fd1a to your computer and use it in GitHub Desktop.

Click and drag above to paint red hexagons. A black outline will appear around contiguous clusters of red hexagons. This outline is constructed using topojson.mesh, part of the TopoJSON client API. A filter is specified so that the mesh only contains boundaries that separate filled hexagons from empty hexagons.

The hexagon grid itself is represented as TopoJSON, but is constructed on-the-fly in the browser. Since TopoJSON requires quantized coordinates, the hexagon grid is represented as integers, with each hexagon of dimensions 3×2. Then a custom projection is used to transform these irregular integer hexagons to normal hexagons of the desired size.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.hexagon {
fill: white;
pointer-events: all;
}
.hexagon path {
-webkit-transition: fill 250ms linear;
transition: fill 250ms linear;
}
.hexagon :hover {
fill: pink;
}
.hexagon .fill {
fill: red;
}
.mesh {
fill: none;
stroke: #000;
stroke-opacity: .2;
pointer-events: none;
}
.border {
fill: none;
stroke: #000;
stroke-width: 2px;
pointer-events: none;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script>
var width = 960,
height = 500,
radius = 20;
var topology = hexTopology(radius, width, height);
var projection = hexProjection(radius);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "hexagon")
.selectAll("path")
.data(topology.objects.hexagons.geometries)
.enter().append("path")
.attr("d", function(d) { return path(topojson.feature(topology, d)); })
.attr("class", function(d) { return d.fill ? "fill" : null; })
.on("mousedown", mousedown)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
svg.append("path")
.datum(topojson.mesh(topology, topology.objects.hexagons))
.attr("class", "mesh")
.attr("d", path);
var border = svg.append("path")
.attr("class", "border")
.call(redraw);
var mousing = 0;
function mousedown(d) {
mousing = d.fill ? -1 : +1;
mousemove.apply(this, arguments);
}
function mousemove(d) {
if (mousing) {
d3.select(this).classed("fill", d.fill = mousing > 0);
border.call(redraw);
}
}
function mouseup() {
mousemove.apply(this, arguments);
mousing = 0;
}
function redraw(border) {
border.attr("d", path(topojson.mesh(topology, topology.objects.hexagons, function(a, b) { return a.fill ^ b.fill; })));
}
function hexTopology(radius, width, height) {
var dx = radius * 2 * Math.sin(Math.PI / 3),
dy = radius * 1.5,
m = Math.ceil((height + radius) / dy) + 1,
n = Math.ceil(width / dx) + 1,
geometries = [],
arcs = [];
for (var j = -1; j <= m; ++j) {
for (var i = -1; i <= n; ++i) {
var y = j * 2, x = (i + (j & 1) / 2) * 2;
arcs.push([[x, y - 1], [1, 1]], [[x + 1, y], [0, 1]], [[x + 1, y + 1], [-1, 1]]);
}
}
for (var j = 0, q = 3; j < m; ++j, q += 6) {
for (var i = 0; i < n; ++i, q += 3) {
geometries.push({
type: "Polygon",
arcs: [[q, q + 1, q + 2, ~(q + (n + 2 - (j & 1)) * 3), ~(q - 2), ~(q - (n + 2 + (j & 1)) * 3 + 2)]],
fill: Math.random() > i / n * 2
});
}
}
return {
transform: {translate: [0, 0], scale: [1, 1]},
objects: {hexagons: {type: "GeometryCollection", geometries: geometries}},
arcs: arcs
};
}
function hexProjection(radius) {
var dx = radius * 2 * Math.sin(Math.PI / 3),
dy = radius * 1.5;
return {
stream: function(stream) {
return {
point: function(x, y) { stream.point(x * dx / 2, (y - (2 - (y & 1)) / 3) * dy / 2); },
lineStart: function() { stream.lineStart(); },
lineEnd: function() { stream.lineEnd(); },
polygonStart: function() { stream.polygonStart(); },
polygonEnd: function() { stream.polygonEnd(); }
};
}
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment