Skip to content

Instantly share code, notes, and snippets.

@flacoman91
Last active February 27, 2020 19:31
Show Gist options
  • Save flacoman91/00d986b6e02ee418c9e195a8390219c5 to your computer and use it in GitHub Desktop.
Save flacoman91/00d986b6e02ee418c9e195a8390219c5 to your computer and use it in GitHub Desktop.
map, pan zoom buttons - convert to d3v4
license: mit

This example shows how to implement zoom in and zoom out buttons on a map using D3 and SVG transforms. Once the target scale and translation are computed, they are applied immediately, and then every 40ms while the button is held down. This implementation seem susceptible to sudden, unpleasant jumps, especially with rapid clicking, even though the callbacks are carefully managed.

This is a fork Mike Bostock's Map Pan & Zoom I; you can diff the two to see my changes. The scaling math (which is quite tricky!) is derived from Wil Linssen.

If zooming by a hard-coded factor would push the scale beyond the scale extent, it is clipped. Importantly, this clipping also affects the translation. If you scale by 1.047 but translate as if you had scaled by 1.2, bad things happen.

See also Zoom Buttons I.

forked from mgold's block: Zoom Buttons II - convert to d3v4

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background: #efefef;
}
.sphere {
fill: #000;
}
.land {
fill: grey;
}
.boundary {
fill: none;
stroke: #660000;
stroke-linejoin: round;
stroke-linecap: round;
vector-effect: non-scaling-stroke;
}
</style>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<body>
<button id="zoom-in">+</button>
<button id="zoom-out">-</button>
<script>
var width = 960,
height = 960,
center = [width / 2, height / 2],
cities = [{ 'code': 'OTT', 'city': 'OTTAWA', 'country': 'CANADA', 'lat': '23.10', 'lon': '120.34' }, { 'code': 'BSB', 'city': 'BRASILIA', 'country': 'BRAZIL', 'lat': '-32.85', 'lon': '133.30' }, { 'code': 'DEL', 'city': 'DELHI', 'country': 'INDIA', 'lat': '4.71', 'lon': '-127.57' }, { 'code': 'CMX', 'city': 'CIDADE DO MÉXICO', 'country': 'MÉXICO', 'lat': '0.42', 'lon': '93.19' }, { 'code': 'SID', 'city': 'SIDNEY', 'country': 'AUSTRALIA', 'lat': '-48.38', 'lon': '-71.71' }, { 'code': 'TOK', 'city': 'TOQUIO', 'country': 'JAPÃO', 'lat': '17.34', 'lon': '-81.73' }, { 'code': 'CCA', 'city': 'CIDADE DO CABO', 'country': 'AFRICA DO SUL', 'lat': '-43.20', 'lon': '-171.97' }, { 'code': 'CMP', 'city': 'CAMPO GRANDE', 'country': 'BRASIL', 'lat': '-36.15', 'lon': '130.72' }, { 'code': 'PAR', 'city': 'PARIS', 'country': 'FRANÇA', 'lat': '22.19', 'lon': '174.27' }, { 'code': 'NOY', 'city': 'NOVA YORK', 'country': 'USA', 'lat': '11.23', 'lon': '112.96' }];
var projection = d3.geoMercator()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.PI);
var zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var g = svg.append("g");
svg.call(zoom);
d3.json("https://gist.githubusercontent.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/world-50m.json", function(error, world) {
g.append("path")
.datum({type: "Sphere"})
.attr("class", "sphere")
.attr("d", path);
g.append("path")
.datum(topojson.merge(world, world.objects.countries.geometries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
const circles = g.selectAll('circle')
.data(cities)
.enter()
.append('a')
.attr('xlink:href', d => `https://www.google.com/search?q=${d.city}`
)
.append('rect')
.attr('x', d => projection([d.lon, d.lat])[0])
.attr('y', d => projection([d.lon, d.lat])[1])
.attr('width', 10)
.attr('height', 10)
.style('fill', 'yellow');
});
function zoomed() {
var transform = d3.event.transform;
g.style("stroke-width", 1.5 / transform.k + "px");
g.attr("transform", transform);
}
d3.select(self.frameElement).style("height", height + "px");
var intervalID;
d3.select('#zoom-in').on('click', function() {
// Smooth zooming
zoom.scaleBy(svg.transition().duration(750), 1.3);
});
d3.select('#zoom-out').on('click', function() {
// Ordinal zooming
zoom.scaleBy(svg, 1 / 1.3);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment