function hexgridGeoPlot(
data = [],
{
features,
position = (d) => [d.lon, d.lat],
width = 640,
height = 400,
projection = d3.geoIdentity().reflectY(true).fitWidth(width, geo),
padding = width / 50,
hexagonRadius = 10,
scale = d3.scaleSequential,
range = interpolateGrBu,
domain,
nice = true,
drawBorder = true,
unknown = "#ccc",
ignorePointsOutsideGeoJson = false,
showLegend = true,
legendTitle,
legendWidth = Math.max(120, width / 7),
ticks,
tickFormat = ".0f",
tickSize = 0,
// Accessors
binValue = (d) => d.datapoints,
binTitle = (d) => d.datapoints,
debug = false
}
) {
// Scales
projection = projection.scale === undefined ? projection() : projection;
projection.fitExtent(
[
[padding, padding],
[width - padding, height - padding]
],
features
);
const pathGenerator = d3.geoPath(projection);
if (ignorePointsOutsideGeoJson) {
data = data.map((d) => {
const [x, y] = projection([d.lon, d.lat]);
return {
...d,
x,
y
};
});
const poly = d3hg.geoPolygon(features, projection);
data = d3hg.polygonPoints(data, poly);
}
const hexgrid = d3hg
.hexgrid()
.extent([width, height])
.geography(features)
.projection(projection)
.hexRadius(hexagonRadius, "km")
.pathGenerator(pathGenerator);
const hex = hexgrid(data, ["name"]);
if (domain === undefined) {
domain = d3.extent(hex.grid.layout, binValue);
if (scale === d3.scaleQuantile) {
domain = hex.grid.layout.map((d) => binValue(d));
}
}
let color = scale(domain, range);
if (nice && typeof color.nice === "function") {
color = color.copy().nice();
}
if (color.unknown && unknown !== undefined) color.unknown(unknown);
// Draw
const svg = DOM.svg(width, height);
d3.select(svg)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.style("background", "white");
if (debug) {
d3.select(svg)
.selectAll(".boundary")
.data(features.features)
.join("path")
.classed("boundary", true)
.attr("d", pathGenerator)
.attr("fill", "#ff0");
}
const bins = d3
.select(svg)
.append("g")
.classed("hexbins", true)
.selectAll("path")
.data(hex.grid.layout)
.join("path")
.attr("d", hex.hexagon())
.attr("transform", (d) => `translate(${d.x} ${d.y})`)
.attr("fill", (d) => color(binValue(d)))
.attr("stroke", (d) => d3.lab(color(binValue(d))).brighter())
.attr("stroke-width", () => (drawBorder ? 0.5 : 0))
.append("title")
.text(binTitle);
if (debug) {
d3.select(svg)
.append("g")
.classed("features", true)
.selectAll("circle")
.data(data)
.join("circle")
.attr(
"transform",
(d) => `translate(${projection(position(d)).join(",")})`
)
.attr("r", 0.75)
.attr("fill", "#f0f")
.attr("stroke", "none");
}
if (showLegend) {
d3.select(svg)
.append("g")
.attr("transform", `translate(${width - legendWidth - padding}, ${padding})`)
.append(() =>
legend({
color,
title: legendTitle,
width: legendWidth,
ticks,
tickFormat,
tickSize
})
);
}
return svg;
}