Skip to content

Instantly share code, notes, and snippets.

@sadaco
Last active November 8, 2015 18:11
Show Gist options
  • Save sadaco/17a5b3e6c5827b90e82b to your computer and use it in GitHub Desktop.
Save sadaco/17a5b3e6c5827b90e82b to your computer and use it in GitHub Desktop.
USA map with "click-to-zoom via transform" and "zoom controls"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1"/>
<title>US Map with TopoJSON</title>
<link rel="stylesheet" type="text/css" href="../../css/styles.css"/>
<script type="text/javascript" src="../../lib/d3.js"></script>
<script type="text/javascript" src="../../lib/topojson.js"></script>
<style>
path {
fill: #dddddd;
stroke: #ffffff;
stroke-width: .5px;
}
path:hover {
fill: #fab440;
}
#us-drilldown{
margin-top: 50px;
}
.ClickState {
fill: #ccc;
cursor: pointer;
}
.ClickState.active {
fill: orange;
}
.ZoomState {
fill: none;
stroke: #fff;
stroke-linecap: round;
stroke-linejoin: round;
}
//Zoom buttons
</style>
</head>
<body>
<div id="us-drilldown"></div>
<script type="text/javascript">
var width = 960,
height = 500,
center = [width / 2, height / 2],
active = d3.select(null);
//Click zoom
var projection = d3.geo.albersUsa()
.scale(1000)
.translate([width / 2, height / 2]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#us-drilldown").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
svg.append("rect")
.on("click", reset);
var g = svg.append('g')
.call(
d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoom)
)
.style("stroke-width", "1px");
svg
.call(zoom)
.call(zoom.event);
//http://bl.ocks.org/mbostock/raw/4090846/us.json
d3.json("../../data/us.json", function (error, topology) {
g.selectAll("path")
.data(topojson.feature(topology, topology.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "ClickState")
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "ZoomState")
.attr("d", path);
});
//Zoom with buttons
function zoomed() {
g.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
}
d3.select(self.frameElement).style("height", height + "px");
// Simplest possible buttons
svg.selectAll(".button")
.data(['zoom_in', 'zoom_out'])
.enter()
.append("rect")
.attr("x", function(d,i){return 10 + 50*i})
.attr({y: 10, width: 20, height: 20, class: "button"})
.attr("id", function(d){return d})
.style("fill", function(d,i){ return i ? "#dddddd" : "#dddddd"})
var intervalID;
d3.selectAll('.button').on('mousedown', function(){
d3.event.preventDefault();
var factor = (this.id === 'zoom_in') ? 1.1 : 1/1.1;
intervalID = setInterval(zoom_by, 40, factor);
}).on('mouseup', function(){
d3.event.preventDefault();
clearInterval(intervalID);
intervalID = undefined;
})
function zoom_by(factor){
var scale = zoom.scale(),
extent = zoom.scaleExtent(),
translate = zoom.translate(),
x = translate[0], y = translate[1],
target_scale = scale * factor;
// If we're already at an extent, done
if (target_scale === extent[0] || target_scale === extent[1]) { return false; }
// If the factor is too much, scale it down to reach the extent exactly
var clamped_target_scale = Math.max(extent[0], Math.min(extent[1], target_scale));
if (clamped_target_scale != target_scale){
target_scale = clamped_target_scale;
factor = target_scale / scale;
}
// Center each vector, stretch, then put back
x = (x - center[0]) * factor + center[0];
y = (y - center[1]) * factor + center[1];
// Enact the zoom immediately
zoom.scale(target_scale)
.translate([x,y]);
zoomed();
}
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1.5px")
.attr("transform", "");
}
function zoom() {
g.attr("transform", "translate("
+ d3.event.translate
+ ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment