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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more