map = {
const { stateQuadtree, countyQuadtree } = quadTrees;
const width = 975;
const height = 610;
const context = DOM.context2d(width, height);
function isInViewport(bounds, transform) {
const [[x0, y0], [x1, y1]] = bounds;
const { x, y, k } = transform;
const padding = 100 / k;
return !(
x1 * k + x < -padding ||
y1 * k + y < -padding ||
x0 * k + x > width + padding ||
y0 * k + y > height + padding
);
}
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);
if (transform.k < 2) {
context.beginPath();
for (let [name, bounds] of stateBounds) {
if (isInViewport(bounds, transform)) {
context.stroke(statePaths.get(name));
}
}
context.strokeStyle = "#000";
context.lineWidth = 1 / transform.k;
context.stroke();
context.beginPath();
for (let [id, bounds] of countyBounds) {
if (isInViewport(bounds, transform)) {
context.stroke(countyPaths.get(id));
}
}
context.strokeStyle = "#aaa";
context.lineWidth = 0.5 / transform.k;
context.stroke();
} else {
// Draw pre-rendered state images with culling
for (let [name, image] of stateImages) {
if (image) {
const bounds = stateBounds.get(name);
if (isInViewport(bounds, transform)) {
const [[x0, y0], [x1, y1]] = bounds;
const width = Math.max(1, x1 - x0);
const height = Math.max(1, y1 - y0);
context.drawImage(image, x0, y0, width, height);
}
}
}
}
// Draw nation
context.strokeStyle = "#000";
context.lineWidth = 1.5 / transform.k;
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);
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];
let frameId;
const interpolator = d3.interpolateZoom(start, end);
const startTime = Date.now();
const duration = 1750;
function frame() {
const t = Math.min(1, (Date.now() - startTime) / duration);
const [x, y, s] = interpolator(d3.easeCubic(t));
const k = width / s;
mutable currentZoom = {
transform: d3.zoomIdentity
.translate(width / 2 - x * k, height / 2 - y * k)
.scale(k),
state: stateName
};
draw();
if (t < 1) frameId = requestAnimationFrame(frame);
}
cancelAnimationFrame(frameId);
frameId = requestAnimationFrame(frame);
}
draw();
if (selectedState !== currentZoom.state) {
zoomToState(selectedState);
}
return context.canvas;
}