Skip to content

Instantly share code, notes, and snippets.

@waleedshkt
Last active April 21, 2020 16:17
Show Gist options
  • Save waleedshkt/c97165f930a409d5643106d9f746bd50 to your computer and use it in GitHub Desktop.
Save waleedshkt/c97165f930a409d5643106d9f746bd50 to your computer and use it in GitHub Desktop.
Zoom a single state with D3 v4
license: gpl-3.0
height: 250
border: no

Renders a single US state, using d3-zoom to fit the container, and to allow zooming and panning.

The desired behavior is to constrain panning to the bounds of the state. The TopoJSON is projected to 600x600, and at that container size, the zoom & pan works great. However, at smaller container sizes, where the map has been scaled, the panning is jumpy/broken. I've tried setting different values for translateExtent, but I don't realy understand how it's supposed to work.

One notable thing is that I'm using a null projection for d3.geoPath, because I used ogr2ogr to generate a shapefile in projected coordinates for each state. That's why I used a zoom transform to fit the map to its container.

References on bl.ocks.org:

var App = (function () {
"use strict";
var App = {
stateCode: window.location.search.substr(1) || 'ma'
};
d3.json("us-" + App.stateCode + ".json", ready);
function ready(error, topo) {
if (error) throw error;
App.state = topojson.feature(topo, topo.objects.state);
App.counties = topojson.feature(topo, topo.objects.counties).features;
App.div = d3.select('#app');
App.size = [
parseInt(App.div.style('width')), parseInt(App.div.style('height'))
];
App.path = d3.geoPath()
.projection(null);
App.transform = zoomBounds(App.path.bounds(App.state), App.size);
App.zoom = d3.zoom()
.translateExtent([[0, 0], App.size])
.scaleExtent([App.transform.k, App.transform.k * 2])
.on("zoom", zoomed);
App.svg = App.div.append("svg")
.call(App.zoom);
App.svg.append("rect")
.classed("background", true)
.on("click", reset);
App.g = App.svg.append("g")
.attr("transform", App.transform);
App.g.selectAll("path")
.data(App.counties)
.enter().append("path")
.classed("county", true)
.attr("d", App.path)
.on("click", clicked);
reset();
}
function zoomBounds(bounds, size) {
// Calculate dimensions of bounds
// and scale to fit inside container
var width = bounds[1][0] - bounds[0][0],
height = bounds[1][1] - bounds[0][1],
scale = 1 / Math.max(width / size[0], height / size[1]);
// Calculate center of bounds
// and difference from center of container
var x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
translate = [size[0] / 2 - scale * x, size[1] / 2 - scale * y];
return d3.zoomIdentity
.translate(translate[0], translate[1])
.scale(scale);
}
function zoomed() {
App.g.attr("transform", d3.event.transform);
}
function reset() {
App.svg.call(App.zoom.transform, App.transform);
}
function clicked() {
App.svg.select(".active")
.classed("active", false);
d3.select(this)
.classed("active", true);
}
return App;
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Zooming</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="app.js"></script>
</body>
#app {
margin: 0 auto;
height: 200px;
width: 300px;
}
/*
@media screen and (min-width: 640px) {
#app {
height: 600px;
width: 600px;
}
}
*/
svg {
border: 1px solid #ccc;
display: block;
height: 100%;
width: 100%;
}
.background {
height: 100%;
width: 100%;
fill: none;
pointer-events: all;
}
.county {
fill: #ccc;
stroke: #fff;
stroke-linejoin: round;
}
.county.active {
fill: orange;
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment