|
var width = 960; |
|
var height = 500; |
|
var 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 hexWidth = radius * 2 * Math.sin(Math.PI / 3); |
|
var hexHeight = radius * 1.5; |
|
var numberOfCellsInHeight = Math.ceil((height + radius) / hexHeight) + 1; |
|
var numberOfCellsInWidth = Math.ceil(width / hexWidth) + 1; |
|
var geometries = []; |
|
var arcs = []; |
|
|
|
for (var yCounter = -1; yCounter <= numberOfCellsInHeight; ++yCounter) { |
|
for (var xCounter = -1; xCounter <= numberOfCellsInWidth; ++xCounter) { |
|
var y = yCounter * 2; |
|
var everySecond = (yCounter & 1); |
|
var x = xCounter * 2 + everySecond; |
|
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 < numberOfCellsInHeight; ++j, q += 6) { |
|
for (var i = 0; i < numberOfCellsInWidth; ++i, q += 3) { |
|
geometries.push({ |
|
type: "Polygon", |
|
arcs: [[ |
|
q, |
|
q + 1, |
|
q + 2, |
|
~(q + (numberOfCellsInWidth + 2 - (j & 1)) * 3), |
|
~(q - 2), |
|
~(q - (numberOfCellsInWidth + 2 + (j & 1)) * 3 + 2) |
|
]], |
|
fill: Math.random() > i / numberOfCellsInWidth * 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); |
|
var 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(); |
|
} |
|
}; |
|
} |
|
}; |
|
} |