Published
Edited
Apr 7, 2021
2 forks
Insert cell
Insert cell
Insert cell
bounds = {
const state_topojson = us.objects.states.geometries.filter(d => d.id == "23")[0];
const state_geojson = topojson.feature(us, state_topojson);
const [[x0, y0], [x1, y1]] = path.bounds(state_geojson);
return [[x0, y0], [x1, y1]];
}
Insert cell
Insert cell
import {Button} from "@observablehq/inputs"
Insert cell
{
// Use D3 to "select" the chart object, which is an SVG element (and JavaScript object)
const svg = d3.select(chart);

// You can access the SVG element's viewbox attribute from JavaScript
// See how d3-zoom does it: https://github.com/d3/d3-zoom/blob/master/src/zoom.js
const {x, y, width, height} = svg.node().viewBox.baseVal;
if (button % 2) {
chart.zoomTo();
return "zoom in";
} else {
chart.zoomReset();
return "reset";
}

return button;
}
Insert cell
mutable z = 42
Insert cell
Insert cell
chart = {
const width = 975;
const height = 610;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const g = svg.append("g");
const zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);

svg.call(zoom);

const states = g.append("g")
.attr("fill", "#444")
.attr("cursor", "pointer")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.join("path")
.attr("d", path);
states.append("title")
.text(d => d.properties.name);

g.append("path")
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path(topojson.mesh(us, us.objects.states, (a, b) => a !== b)));
function zoomed({transform}) {
g.attr("transform", transform);
mutable z = svg.node().__zoom
}
function reset() {
svg.transition().call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([width / 2, height / 2])
);
mutable z = svg.node().__zoom
}
function zoomToState() {
const [[x0, y0], [x1, y1]] = bounds;

// Note: the topojson comes with the origin in the upper left. By default,
// the reference point for zoom.scale is the center of the viewport extent.
// So if you're going to rescale relative to default, you need to translate
// the origin of the topojson to the center of the viewport extent.
// Therefore, the transform sequence involves 3 steps:
// 1. Move the origin to the center viewport extent
// 2. Rescale the map
// 3. Translate to new location to the center of the viewport extent
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2) // Step 1
.scale(Math.min(8, 0.9 / Math.max((x1 - x0) / width, (y1 - y0) / height))) // Step 2
.translate(-(x0 + x1) / 2, -(y0 + y1) / 2) // Step 3
);
}

return Object.assign(svg.node(), {
zoomIn: () => svg.transition().call(zoom.scaleBy, 2),
zoomOut: () => svg.transition().call(zoom.scaleBy, 0.5),
zoomTo: () => zoomToState(),
zoomReset: () => reset(),
});
}
Insert cell
Insert cell
path = d3.geoPath()
Insert cell
us = FileAttachment("states-albers-10m.json").json()
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@6")
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