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.
forked from mbostock's block: Hexagon Mesh
forked from ericsoco's block: Hexagon Mesh
xxxxxxxxxx
<meta charset="utf-8">
<style>
circle,
.hexagon {
fill: none;
pointer-events: all;
}
.hexagon path {
-webkit-transition: fill 250ms linear;
transition: fill 250ms linear;
}
.hexagon :hover {
fill: pink;
}
circle.fill,
.hexagon .fill {
fill: red;
}
.mesh {
fill: none;
stroke: #000;
stroke-opacity: .05;
pointer-events: none;
}
.border {
fill: none;
stroke: #000;
stroke-width: 2px;
stroke-opacity: 0;
pointer-events: none;
}
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500,
radius = 50;
var topology = hexTopology(radius, width, height);
var projection = hexProjection(radius);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//SVG filter for the gooey effect
//Code taken from https://tympanus.net/codrops/2015/03/10/creative-gooey-effects/
var defs = svg.append('defs');
var filter = defs.append('filter').attr('id','gooey');
filter.append('feGaussianBlur')
.attr('in','SourceGraphic')
.attr('stdDeviation','10')
.attr('result','blur');
filter.append('feColorMatrix')
.attr('in','blur')
.attr('mode','matrix')
.attr('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7')
.attr('result','gooey');
// filter.append("feBlend")
// .attr("in","SourceGraphic")
// .attr("in2","gooey");
filter.append('feComposite')
.attr('in','SourceGraphic')
.attr('in2','gooey')
.attr('operator','atop');
var hexes = svg.append("g")
.attr("class", "hexagon")
.style("filter", "url(#gooey)")
.selectAll("path")
.data(topology.objects.hexagons.geometries).enter();
hexes.append("path")
.attr("class", function(d) { return "i"+d.i+"j"+d.j; })
.classed("hex", true)
.classed("fill", function(d) { return d.fill; })
.attr("d", function(d) { return path(topojson.feature(topology, d)); });
var col = d3.interpolateRdYlGn;
// var col = d3.interpolatePlasma;
d3.selectAll(".hex")
.attr("fill", function (d) {
let dist = (Math.abs(d.i - 5))/10 + Math.abs(d.j - 4)/8;
// let dist = Math.max(Math.abs(d.i - 5)/10, Math.abs(d.j - 4)/8);
return col(dist*1.5);
})
.attr("transform", function (d) { return scaleHex(d, 0); });
var step = 1500;
setInterval(function () { animate(); }, 2*step);
animate();
function scaleHex (d, s) {
cx = (d.i + ((d.j%2) ? 0 : 0.5)) * radius * 2 * Math.sin(Math.PI / 3);
cy = (d.j - 1) * radius * 1.5;
return "matrix("+s+", 0, 0, "+s+", "+(cx-s*cx)+"," +(cy-s*cy)+")";
}
function animate () {
svg.selectAll(".i5j4")
.attr("transform", function (d) { return scaleHex(d, 1.0); });
svg.selectAll(
".i5j3, .i6j4, .i5j5," +
".i6j2,.i7j3, .i6j6,.i5j6, .i3j4,.i4j3," +
".i4j1,.i5j1,.i6j1, .i8j4,.i8j5,.i7j6, .i4j7,.i3j6,.i3j5," +
".i3j1,.i2j2,.i2j3,.i1j4, .i9j3,.i8j2,.i8j1,.i7j0, .i4j8,.i5j8,.i6j8,.i7j8")
.attr("transform", function (d) { return scaleHex(d, 0.25); })
.style("opacity", 0.5)
.transition()
.delay(step/2)
.ease(d3.easeQuadInOut)
.duration(step)
.attr("transform", function (d) { return scaleHex(d, 1); })
.style("opacity", 1.0)
.transition()
.ease(d3.easeQuadInOut)
.duration(step)
.attr("transform", function (d) { return scaleHex(d, 0.25); })
.style("opacity", 0.5);
svg.selectAll(
".i6j3, .i4j4, .i6j5," +
".i4j2,.i5j2, .i7j4,.i7j5, .i4j6,.i4j5," +
".i7j1,.i7j2,.i8j3, .i5j7,.i6j7,.i7j7, .i2j4,.i3j3,.i3j2," +
".i9j4,.i9j5,.i8j6,.i8j7, .i3j8,.i3j7,.i2j6,.i2j5")
.attr("transform", function (d) { return scaleHex(d, 0.25); })
.style("opacity", 0.5)
.transition()
.delay(step)
.ease(d3.easeQuadInOut)
.duration(2/3*step)
.attr("transform", function (d) { return scaleHex(d, 1); })
.style("opacity", 1.0)
.transition()
.ease(d3.easeQuadInOut)
.duration(4/3*step)
.attr("transform", function (d) { return scaleHex(d, 0.25); })
.style("opacity", 0.5);
}
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: false,//i > n / 2,
j: j,
i: i
});
}
}
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>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js
https://d3js.org/topojson.v1.min.js