A simple implementation of fog of war using hexbinning. Mouseover the darkened region to reveal the data (or lack of data) underneath. If you mouse over a region with no data, it will reveal any hexes nearby that also don't have data underneath.
| (function() { | |
| d3.hexbin = function() { | |
| var width = 1, | |
| height = 1, | |
| r, | |
| x = d3_hexbinX, | |
| y = d3_hexbinY, | |
| dx, | |
| dy; | |
| function hexbin(points) { | |
| var binsById = {}; | |
| points.forEach(function(point, i) { | |
| var py = y.call(hexbin, point, i) / dy, pj = Math.round(py), | |
| px = x.call(hexbin, point, i) / dx - (pj & 1 ? .5 : 0), pi = Math.round(px), | |
| py1 = py - pj; | |
| if (Math.abs(py1) * 3 > 1) { | |
| var px1 = px - pi, | |
| pi2 = pi + (px < pi ? -1 : 1) / 2, | |
| pj2 = pj + (py < pj ? -1 : 1), | |
| px2 = px - pi2, | |
| py2 = py - pj2; | |
| if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) pi = pi2 + (pj & 1 ? 1 : -1) / 2, pj = pj2; | |
| } | |
| var id = pi + "-" + pj, bin = binsById[id]; | |
| if (bin) bin.push(point); else { | |
| bin = binsById[id] = [point]; | |
| bin.i = pi; | |
| bin.j = pj; | |
| bin.x = (pi + (pj & 1 ? 1 / 2 : 0)) * dx; | |
| bin.y = pj * dy; | |
| } | |
| }); | |
| return d3.values(binsById); | |
| } | |
| function hexagon(radius) { | |
| var x0 = 0, y0 = 0; | |
| return d3_hexbinAngles.map(function(angle) { | |
| var x1 = Math.sin(angle) * radius, | |
| y1 = -Math.cos(angle) * radius, | |
| dx = x1 - x0, | |
| dy = y1 - y0; | |
| x0 = x1, y0 = y1; | |
| return [dx, dy]; | |
| }); | |
| } | |
| hexbin.x = function(_) { | |
| if (!arguments.length) return x; | |
| x = _; | |
| return hexbin; | |
| }; | |
| hexbin.y = function(_) { | |
| if (!arguments.length) return y; | |
| y = _; | |
| return hexbin; | |
| }; | |
| hexbin.hexagon = function(radius) { | |
| if (arguments.length < 1) radius = r; | |
| return "m" + hexagon(radius).join("l") + "z"; | |
| }; | |
| hexbin.hexagonArray = function(radius) { | |
| if (arguments.length < 1) radius = r; | |
| return hexagon(radius); | |
| }; | |
| hexbin.centers = function() { | |
| var centers = []; | |
| for (var y = 0, odd = false, j = 0; y < height + r; y += dy, odd = !odd, ++j) { | |
| for (var x = odd ? dx / 2 : 0, i = 0; x < width + dx / 2; x += dx, ++i) { | |
| var center = [x, y]; | |
| center.i = i; | |
| center.j = j; | |
| centers.push(center); | |
| } | |
| } | |
| return centers; | |
| }; | |
| hexbin.mesh = function() { | |
| var fragment = hexagon(r).slice(0, 4).join("l"); | |
| return hexbin.centers().map(function(p) { return "M" + p + "m" + fragment; }).join(""); | |
| }; | |
| hexbin.size = function(_) { | |
| if (!arguments.length) return [width, height]; | |
| width = +_[0], height = +_[1]; | |
| return hexbin; | |
| }; | |
| hexbin.radius = function(_) { | |
| if (!arguments.length) return r; | |
| r = +_; | |
| dx = r * 2 * Math.sin(Math.PI / 3); | |
| dy = r * 1.5; | |
| return hexbin; | |
| }; | |
| return hexbin.radius(1); | |
| }; | |
| var d3_hexbinAngles = d3.range(0, 2 * Math.PI, Math.PI / 3), | |
| d3_hexbinX = function(d) { return d[0]; }, | |
| d3_hexbinY = function(d) { return d[1]; }; | |
| })(); |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>Fog of War with Hexbin</title> | |
| <style> | |
| </style> | |
| </head> | |
| <body> | |
| <svg width="500" height="500" /> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script> | |
| <script src="d3.hexbin.js" charset="utf-8" type="text/javascript"></script> | |
| <script type="text/javascript"> | |
| vertices = [[162, 332], [182, 299], [141, 292], [158, 264], [141, 408], [160, 400], [177, 430], [151, 442], [155, 425], [134, 430], [126, 447], [139, 466], [160, 471], [167, 447], [182, 466], [192, 442], [187, 413], [173, 403], [168, 425], [153, 413], [179, 275], [163, 292], [134, 270], [143, 315], [177, 320], [163, 311], [162, 281], [182, 255], [141, 226], [156, 235], [173, 207], [187, 230], [204, 194], [165, 189], [145, 201], [158, 167], [190, 165], [206, 145], [179, 153], [204, 114], [221, 138], [243, 112], [248, 139], [177, 122], [179, 99], [196, 82], [219, 90], [240, 75], [218, 61], [228, 53], [211, 34], [197, 51], [179, 65], [155, 70], [165, 85], [134, 80], [124, 58], [153, 44], [173, 34], [192, 27], [156, 19], [119, 32], [128, 17], [138, 36], [100, 58], [112, 73], [100, 92], [78, 100], [83, 78], [61, 63], [80, 44], [100, 26], [60, 39], [43, 71], [34, 54], [32, 90], [53, 104], [60, 82], [66, 99], [247, 94], [187, 180], [221, 168]]; | |
| hexbin = d3.hexbin() | |
| .size([500,500]) | |
| .radius(25) | |
| .x(function (d) {return d.x}) | |
| .y(function (d) {return d.y}) | |
| fullpoints = []; | |
| vertices.forEach(function (d) { | |
| fullpoints.push({x: d[0], y: d[1], type: "data"}) | |
| }) | |
| d3.range(20).forEach(function (d) { | |
| d3.range(20).forEach(function (p) { | |
| fullpoints.push({x: d * 25, y: p * 25, type: "hex"}) | |
| }) | |
| }) | |
| hexdata = hexbin(fullpoints); | |
| d3.select("svg").selectAll("circle") | |
| .data(fullpoints.filter(function (d) {return d.type === "data"})) | |
| .enter() | |
| .append("circle") | |
| .attr("cx", function (d) {return d.x}) | |
| .attr("cy", function (d) {return d.y}) | |
| .style("fill", "#001276") | |
| .attr("r", 2); | |
| d3.select("svg").selectAll("path") | |
| .data(hexdata) | |
| .enter() | |
| .append("path") | |
| .attr("d", function (d) {return hexbin.hexagon(25); }) | |
| .attr("transform", function(d) { console.log(d); return "translate(" + d.x + "," + d.y + ")"; }) | |
| .style("fill", "#001276") | |
| .style("stroke", "#001276") | |
| .on("mouseover", defog) | |
| function defog(d) { | |
| d3.select(this) | |
| .transition() | |
| .duration(1000) | |
| .style("opacity", 0); | |
| d3.selectAll("path") | |
| .filter(function (p) { | |
| return Math.abs(p.x - d.x) < 65 && Math.abs(p.y - d.y) < 65 && d.filter(function (d) {return d.type === "data"}).length === 0 && p.filter(function (p) {return p.type === "data"}).length === 0; | |
| }) | |
| .transition() | |
| .duration(1000) | |
| .style("opacity", 0); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment