index.html
<html>
<head>
<style>
path {
stroke:white;
stroke-width: 0.5px;
fill:#EBEBE0;
}
#float-button-group {
position:fixed;
left:10;
opacity:0.5;
}
#float-button-group:hover {
opacity:1;
}
#map {
background: skyblue;
width: 965px;
height: 505px;
border: 3px solid steelblue;
}
#map-zoomer {
position:absolute;
writing-mode: bt-lr;
-webkit-appearance: slider-vertical;
width: 8px;
height: 100px;
padding: 0 5px;
position: absolute;
top:110px;
left:22px;
}
</style>
</head>
<body>
<div id="map">
<div class="btn-group-vertical" role="group" aria-label="..." id="float-button-group">
<button type="button" class="btn btn-default" id="zoom-in"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></button>
<button type="button" class="btn btn-default" id="zoom-out"><span class="glyphicon glyphicon-zoom-out" aria-hidden="true"></span></button>
<button type="button" class="btn btn-default" id="reset"><span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span></button>
</div>
<input type="range" value="1" min="1" max="8" orient="vertical" id="map-zoomer"/>
</div>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v0.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<script>
var width = d3.select('#map').node().getBoundingClientRect().width,
height = d3.select('#map').node().getBoundingClientRect().height;
var center = [width / 2, height / 2];
var projection = d3.geo.mercator()
.center([0, 10]);
var svg = d3.select("#map")
.append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
d3.json("world-110m2.json", function (error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries).geometries)
.enter()
.append("path")
.attr("d", path);
d3.csv("cities.csv", function (error, cities) {
svg.selectAll("circle")
.data(cities)
.enter()
.append("circle")
.attr("cx", function (d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function (d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "red");
});
});
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", function () {
g.attr("transform", "translate(" + d3.event.translate.join(",") + ") scale(" + d3.event.scale + ")");
g.selectAll("path")
.attr("d", path.projection(projection));
svg.selectAll("circle")
.attr("transform", "translate(" + d3.event.translate.join(",") + ") scale(" + d3.event.scale + ")")
.transition()
.attr("r", 5 / d3.event.scale);
d3.select("#map-zoomer").node().value = zoom.scale();
});
svg.call(zoom);
d3.select('#zoom-in').on('click', function () {
var scale = zoom.scale(), extent = zoom.scaleExtent(), translate = zoom.translate();
var x = translate[0], y = translate[1];
var factor = 1.2;
var target_scale = scale * factor;
if (scale === extent[1]) {
return false;
}
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;
}
x = (x - center[0]) * factor + center[0];
y = (y - center[1]) * factor + center[1];
zoom.scale(target_scale).translate([x, y]);
g.transition().attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")");
g.selectAll("path")
.attr("d", path.projection(projection));
svg.selectAll("circle")
.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")")
.attr("r", 5 / zoom.scale());
d3.select("#map-zoomer").node().value = zoom.scale();
});
d3.select('#zoom-out').on('click', function () {
var scale = zoom.scale(), extent = zoom.scaleExtent(), translate = zoom.translate();
var x = translate[0], y = translate[1];
var factor = 1 / 1.2;
var target_scale = scale * factor;
if (scale === extent[0]) {
return false;
}
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;
}
x = (x - center[0]) * factor + center[0];
y = (y - center[1]) * factor + center[1];
zoom.scale(target_scale).translate([x, y]);
g.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")");
g.selectAll("path")
.attr("d", path.projection(projection));
svg.selectAll("circle")
.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")")
.attr("r", 5 / zoom.scale());
d3.select("#map-zoomer").node().value = zoom.scale();
});
d3.select('#reset').on('click', function () {
zoom.translate([0, 0]);
zoom.scale(1);
g.transition().attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")");
g.selectAll("path")
.attr("d", path.projection(projection))
svg.selectAll("circle")
.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")")
.transition()
.attr("r", 5 / zoom.scale());
d3.select("#map-zoomer").node().value = zoom.scale();
});
d3.select('#map-zoomer').on("change", function () {
var scale = zoom.scale(), extent = zoom.scaleExtent(), translate = zoom.translate();
var x = translate[0], y = translate[1];
var target_scale = +this.value;
var factor = target_scale / scale;
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;
}
x = (x - center[0]) * factor + center[0];
y = (y - center[1]) * factor + center[1];
zoom.scale(target_scale).translate([x, y]);
g.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")");
g.selectAll("path")
.attr("d", path.projection(projection));
svg.selectAll("circle")
.transition()
.attr("transform", "translate(" + zoom.translate().join(",") + ") scale(" + zoom.scale() + ")")
.attr("r", 5 / zoom.scale());
});
</script>
</body>
</html>
cities.csv
code,city,country,lat,lon
ZNZ,ZANZIBAR,TANZANIA,-6.13,39.31
TYO,TOKYO,JAPAN,35.68,139.76
AKL,AUCKLAND,NEW ZEALAND,-36.85,174.78
BKK,BANGKOK,THAILAND,13.75,100.48
DEL,DELHI,INDIA,29.01,77.38
SIN,SINGAPORE,SINGAPOR,1.36,103.75
BSB,BRASILIA,BRAZIL,-15.67,-47.43
RIO,RIO DE JANEIRO,BRAZIL,-22.90,-43.24
YTO,TORONTO,CANADA,43.64,-79.40
IPC,EASTER ISLAND,CHILE,-27.11,-109.36
SEA,SEATTLE,USA,47.61,-122.33