Public
Edited
May 2
Fork of Hexbin map
Insert cell
Insert cell
chart = {

const width = 928;
const height = 581;

// 1. Define the projection first
const projection = d3.geoAlbersUsa()
.scale(4 / 3 * width)
.translate([width / 2, height / 2]);

// 2. Then compute the projected points
const projected = walmarts
.map(d => {
const p = projection([d.longitude, d.latitude]);
return p ? {xy: p, date: d.date} : null;
})
.filter(d => d !== null && d.date.getUTCFullYear() >= 1700);

// 3. Create the SVG container
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

// 4. Hexbin setup
const hexbin = d3.hexbin()
.extent([[0, 0], [width, height]])
.radius(10)
.x(d => d.xy[0])
.y(d => d.xy[1]);

const bins = hexbin(projected)
.map(d => (d.date = new Date(d3.median(d, d => d.date)), d))
.sort((a, b) => b.length - a.length);

// 5. Color and radius scales
const color = d3.scaleSequential(d3.extent(bins, d => d.date), d3.interpolateSpectral);
const radius = d3.scaleSqrt([0, d3.max(bins, d => d.length)], [0, hexbin.radius() * Math.SQRT2]);

// 6. Color legend
svg.append("g")
.attr("transform", "translate(580,20)")
.append(() => legend({
color,
title: "Median year of paranormal activity",
width: 300,
tickValues: d3.utcYear.every(100).range(...color.domain()),
tickFormat: d3.utcFormat("%Y")
}));

// 7. State mesh
svg.append("path")
.datum(stateMesh)
.attr("fill", "none")
.attr("stroke", "#777")
.attr("stroke-width", 0.5)
.attr("stroke-linejoin", "round")
.attr("d", d3.geoPath(projection));

// 8. Hexagons
svg.append("g")
.selectAll("path")
.data(bins)
.join("path")
.attr("transform", d => `translate(${d.x},${d.y})`)
.attr("d", d => hexbin.hexagon(radius(d.length)))
.attr("fill", d => color(d.date))
.attr("stroke", d => d3.lab(color(d.date)).darker())
.append("title")
.text(d => `${d.length.toLocaleString()} haunted reports\n${d.date.getFullYear()} median report`);

return svg.node();
}
Insert cell
walmarts = {
const parseDate = d3.utcParse("%m/%d/%Y");
return FileAttachment("hexbin_data.tsv").tsv()
.then(data => data.map((d) => ({
longitude: +d[0],
latitude: +d[1],
date: parseDate(d.date)
})));
}
Insert cell
stateMesh = FileAttachment("us-counties-10m.json").json().then(us => topojson.mesh(us, us.objects.states))
Insert cell
d3 = require("d3@7", "d3-hexbin@0.2")
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
Insert cell
Plot.plot({
projection: "albers-usa",
color: {scheme: "Spectral"},
marks: [
Plot.geo(stateMesh, {strokeOpacity: 0.25}),
Plot.dot(walmarts, Plot.hexbin(
{fill: "median", r: "count"},
{x: "longitude", y: "latitude", binWidth: 14, fill: "date", stroke: "currentColor", strokeWidth: 0.5}
))
]
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more