Public
Edited
Jul 19, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map = {
const width = 975;
const height = 610;
const context = DOM.context2d(width, height);

function draw() {
const { transform } = currentZoom;
context.clearRect(0, 0, width, height);
context.save();
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);

// Draw visible counties
context.beginPath();
context.strokeStyle = "#aaa";
context.lineWidth = 0.5 / transform.k;
for (let [id, bounds] of countyBounds) {
if (isInViewport(bounds, transform)) {
context.stroke(countyPaths.get(id));
}
}

// Draw visible states
context.beginPath();
context.strokeStyle = "#000";
context.lineWidth = 1 / transform.k; // Slightly thicker for states
for (let [name, bounds] of stateBounds) {
if (isInViewport(bounds, transform)) {
context.stroke(statePaths.get(name));
}
}

// Draw nation
context.beginPath();
context.strokeStyle = "#000";
context.lineWidth = 1.5 / transform.k; // Even thicker for nation
context.stroke(nationPath);

// Highlight selected state
if (currentZoom.state) {
const selectedStatePath = statePaths.get(currentZoom.state);
if (selectedStatePath) {
context.fillStyle = "rgba(255, 0, 0, 0.2)";
context.fill(selectedStatePath);
}
}

context.restore();
}

// The rest of your code (zoomToState function, etc.) remains the same
function zoomToState(stateName) {
const state = topojson
.feature(us, us.objects.states)
.features.find((d) => d.properties.name === stateName);
// coordinates of the next state
const [[x0, y0], [x1, y1]] = path.bounds(state);

const k = Math.min(
8,
0.9 / Math.max((x1 - x0) / width, (y1 - y0) / height)
);

const centroid = [(x0 + x1) / 2, (y0 + y1) / 2];

const currentTransform = currentZoom.transform;
const start = [
(width / 2 - currentTransform.x) / currentTransform.k,
(height / 2 - currentTransform.y) / currentTransform.k,
width / currentTransform.k
];
const end = [centroid[0], centroid[1], width / k];

d3.transition()
.duration(1750)
.tween("zoom", () => (t) => {
const [x, y, s] = d3.interpolateZoom(start, end)(t);
const k = width / s;
mutable currentZoom = {
transform: d3.zoomIdentity
.translate(width / 2 - x * k, height / 2 - y * k)
.scale(k),
state: stateName
};
draw();
})
.ease(d3.easeCubic);
}
draw();
if (selectedState !== currentZoom.state) {
zoomToState(selectedState);
}
return context.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function isInViewport(bounds, transform) {
const [[x0, y0], [x1, y1]] = bounds;
const { x, y, k } = transform;
const padding = 100 / k; // Add some padding to prevent pop-in
return !(
x1 * k + x < 0 ||
y1 * k + y < 0 ||
x0 * k + x > width ||
y0 * k + y > height
);
}
Insert cell
stateBounds = new Map(topojson.feature(us, us.objects.states).features
.map(f => [f.properties.name, path.bounds(f)]));
Insert cell
countyBounds = new Map(topojson.feature(us, us.objects.counties).features
.map(f => [f.id, path.bounds(f)]));
Insert cell
countyPaths = new Map(topojson.feature(us, us.objects.counties).features.map(feature =>
[feature.id, new Path2D(path(feature))]
));
Insert cell
statePaths = new Map(topojson.feature(us, us.objects.states).features.map(feature =>
[feature.properties.name, new Path2D(path(feature))]
));
Insert cell
// pre-projected
// us = FileAttachment("counties-albers-10m.json").json()
Insert cell
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