Last active
March 14, 2019 11:16
-
-
Save sarah37/17796e35d845d500f55d8a4255f4777b to your computer and use it in GitHub Desktop.
Zoomable Dot Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
location | lat | lon | |
---|---|---|---|
China | 35.86166 | 104.195397 | |
Australia | -25.274398 | 133.775136 | |
France | 44.734828 | 0.408447 | |
The Netherlands | 52.120831 | 4.51657 | |
North Atlantic Ocean | 48.978078 | -24.896729 | |
China | 23.12911 | 113.264385 | |
USA | 42.2842 | -74.375914 | |
New Zealand | -38.059426 | 175.437557 | |
USA | 33.233858 | -84.054441 | |
Brazil | -14.54628 | -52.794109 | |
USA | 39.400183 | -101.291831 | |
Zimbabwe | -19.015438 | 29.154857 | |
USA | 40.680428 | -122.370842 | |
Argentina | -38.416097 | -63.616672 | |
Vietnam | 10.103407 | 105.501645 | |
India | 20.593684 | 78.96288 | |
Ethiopia | 9.145 | 40.489673 | |
Japan | 35.961719 | 137.546605 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Zoomable dot map</title> | |
<link rel="stylesheet" href="style.css"> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
</head> | |
<body> | |
<div id="mapDiv"></div> | |
<script type="text/javascript" src="map.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// set width and height | |
var w = 960 | |
var h = 500 | |
// create svg | |
var svg = d3.select("#mapDiv") | |
.append("svg") | |
.attr("width", w) | |
.attr("height", h); | |
// initial scale and translation (makes mercator projection fit screen) | |
scaleInit = h/(2*Math.PI) * 1.3 | |
transInit = [w/2, h/2] | |
// define projection | |
var projection = d3.geoMercator() | |
.scale(scaleInit) | |
.translate(transInit) | |
// define path generator | |
var path = d3.geoPath() | |
.projection(projection); | |
// g's for different parts of the map | |
var mapG = svg.append("g") | |
var dotG = svg.append("g") | |
// load world map geojson | |
d3.json("https://gist.githubusercontent.com/sarah37/dcca42b936545d9ee9f0bc8052e03dbd/raw/550cfee8177df10e515d82f7eb80bce4f72c52de/world-110m.json").then(function(world) { | |
mapG | |
.append("path") | |
.attr("d", path(world)) | |
.classed("land", true) | |
// load dataset for dots | |
d3.csv('example_locations.csv').then(function(data) { | |
// initialise zoom | |
// has to be within recall of dataset because "zoomEnd" function triggers reloading the dots | |
var zoom = d3.zoom() | |
.on("start", zoomStart) | |
.on("zoom", zooming) | |
.on("end", zoomEnd) | |
svg.call(zoom) | |
updateDots() | |
function zoomStart() { | |
dotG.classed("hidden", true) | |
} | |
function zooming() { | |
// zoom map | |
mapG.style("stroke-width", 1.5 / d3.event.transform.k + "px"); | |
mapG.attr("transform", d3.event.transform); | |
} | |
function zoomEnd() { | |
dotG.classed("hidden", false) | |
// update projection | |
projection | |
.translate([d3.event.transform.x + d3.event.transform.k*transInit[0], d3.event.transform.y + d3.event.transform.k*transInit[1]]) | |
.scale(d3.event.transform.k * scaleInit) | |
// re-plot dots | |
updateDots() | |
} | |
function updateDots() { | |
// get current bbox | |
var bbox = getBoundingBox() | |
// filter data for the visible dots only | |
var visible = data.filter(function(d) { | |
return filterBBox(bbox, d.lon, d.lat) | |
}) | |
// bind new data to circles | |
var circle = dotG | |
.selectAll(".dot") | |
.data(visible) | |
// remove surplus circles | |
circle.exit().remove() | |
// add new ones | |
circle | |
.enter() | |
.append("circle") | |
.classed("dot", true) | |
.merge(circle) | |
.attr("cx", function(d) { | |
d.loc = projection([d.lon, d.lat]) | |
return d.loc[0] | |
}) | |
.attr("cy", function(d) {return d.loc[1]}) | |
.attr("r", 3) | |
} | |
}) | |
.catch(function(error){ | |
throw error; | |
}) | |
}) | |
.catch(function(error){ | |
throw error; | |
}) | |
function getBoundingBox() { | |
// computes bounding box of currently visible part of the map in terms of lat/lon | |
var mapBounds = mapG.node().getBBox() | |
var mapTrans = d3.zoomTransform(svg.node()) | |
var l = mapTrans.x + mapBounds.x * mapTrans.k | |
var r = l + mapBounds.width * mapTrans.k | |
var t = mapTrans.y + mapBounds.y * mapTrans.k | |
var b = t + mapBounds.height * mapTrans.k | |
// compare to bounds of svg (visible part) | |
l = (l > 0 ? -180 : projection.invert([0, NaN])[0]) | |
r = (r < w ? 180 : projection.invert([w, NaN])[0]) | |
t = (t > 0 ? 85 : projection.invert([NaN, 0])[1]) | |
b = (b < h ? -85 : projection.invert([NaN, h])[1]) | |
return {l: l, r: r, t: t, b: b} | |
} | |
function filterBBox(bbox, lon, lat) { | |
// check if point at lon/lat is within the visible part of the map | |
return (bbox.l < lon && lon < bbox.r && bbox.b < lat && lat < bbox.t) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.land { | |
fill: #eee; | |
stroke: #bbb; | |
} | |
circle { | |
fill: #D94726; | |
} | |
.hidden { | |
display: none; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment