Skip to content

Instantly share code, notes, and snippets.

@hollasch
Forked from patricksurry/README.md
Last active August 12, 2016 23:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hollasch/12e6627b4a8d7c3ceaac5297fa1d3169 to your computer and use it in GitHub Desktop.
Save hollasch/12e6627b4a8d7c3ceaac5297fa1d3169 to your computer and use it in GitHub Desktop.
World Map with Zoom and Continuous Horizontal Pan

This gist illustrates a world map with zoom and continuous (infinite) horizontal panning. Dragging left/right rotates the projection cylinder, and dragging up/down translates the projection accordingly (clamped by latitude bounds).

The viewbox is the full width of the page.

This example still has an annoying issue with zooms. In my ideal world, the map should zoom centered on the cursor location. Unfortunately, the map just changes scale centered on the equator and the longitude at the center of the viewport. I haven't yet been able to find a way to make this work (using D3 v3).

Source

This example is based on Patrick Surry's example Rolling pan and zoom with Mercator projection.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background-color: #bbbbff;
border: 2px solid #888888;
}
path {
fill: #e0f0d4;
stroke: #666;
stroke-width: .5px;
}
.overlay {
fill: none;
pointer-events: all;
}
#map {
margin-top: 2em;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<div id="map"></div>
<script>
console.log ('Loading map pan/zoom example.');
var mapDiv = d3.select('#map');
var width = mapDiv.node().getBoundingClientRect().width;
var height = 0.35 * width;
var plotCenter = [ width/2, height/2 ];
var initialLongitude = -95; // Initial longitude to center
var latitudeBounds = [ -80, 84 ]; // Maximum latitude to display
var projection = d3.geo.mercator()
.rotate([-initialLongitude, 0]) // Rotate the initial longitude to center
.scale(1) // We'll scale up to match the viewport shortly
.translate(plotCenter);
var viewMin = [ 0, 0 ];
var viewMax = [ 0, 0 ];
function updateProjectionBounds () {
// Updates the view top left and bottom right with the current projection.
var yaw = projection.rotate()[0];
var longitudeHalfRotation = 180.0 - 1e-6;
viewMin = projection([-yaw - longitudeHalfRotation, latitudeBounds[1]]);
viewMax = projection([-yaw + longitudeHalfRotation, latitudeBounds[0]]);
}
updateProjectionBounds();
// Set up the scale extent and initial scale for the projection.
var s = width / (viewMax[0] - viewMin[0]);
var scaleExtent = [s, 50*s]; // The minimum and maximum zoom scales
projection
.scale(scaleExtent[0]); // Set up projection to minimium zoom
var path = d3.geo.path() // Map Geometry
.projection(projection);
var svg = mapDiv.append('svg') // Set up map SVG element
.attr('width',width)
.attr('height',height)
var map = svg.append('g'); // Map Group
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height);
var zoom = d3.behavior.zoom() // Set up zoom
.size([width,height])
.scaleExtent(scaleExtent)
.scale(projection.scale())
.on("zoom", handlePanZoom);
svg.call(zoom); // Attach zoom event
// Load map data
d3.json("world-50m.json", function (error, world) {
map.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append('path');
render();
});
// The following variables track the last processed event.
var translateLast = [0,0];
var scaleLast = null;
function render() {
map.selectAll('path') // Redraw all map paths
.attr('d', path);
}
function handlePanZoom() {
// Handle pan and zoom events
var scale = zoom.scale();
var translate = zoom.translate();
// If the scaling changes, ignore translation (otherwise touch zooms are weird).
var delta = [ translate[0] - translateLast[0], translate[1] - translateLast[1] ];
if (scale != scaleLast) {
projection.scale(scale);
} else {
var longitude = projection.rotate()[0];
var tp = projection.translate();
// Use the X translation to rotate, based on the current scale.
longitude += 360 * (delta[0] / width) * (scaleExtent[0] / scale);
projection.rotate ([longitude, 0, 0]);
// Use the Y translation to translate projection, clamped by min/max
updateProjectionBounds();
if (viewMin[1] + delta[1] > 0)
delta[1] = -viewMin[1];
else if (viewMax[1] + delta[1] < height)
delta[1] = height - viewMax[1];
projection.translate ([ tp[0], tp[1] + delta[1] ]);
}
// Store the last transform values. NOTE: Resetting zoom.translate() and zoom.scale()
// would seem equivalent, but it doesn't seem to work reliably.
scaleLast = scale;
translateLast = translate;
render();
}
</script>
Display the source blob
Display the rendered blob
Raw
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