Map of US military installations around the world. Most of the data comes from the 2013 Base Structure Report. I didn't collate or create the data, I am just practicing mapping things I find interesting. The much more sophisticated version of this visualization, and the original, can be found at http://empire.is and was created by Josh Begley. I highly recommend you check it out.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Military Bases</title>
<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
</head>
<body>
<div id="title">US Military Installations </div>
</body>
<footer>
<script>
var height = 500
var height = 500
var width = 500
var scale = 225
d3.select("body").append("g").attr("id","Chart")
d3.select("g#Chart").append("svg")
.attr("width",width)
.attr("height",height)
var g = d3.select("svg").append("g")
var promiseWrapper = (xhr, d) => new
Promise(resolve => xhr(d, (p) => resolve(p)))
Promise.all([promiseWrapper(d3.json, "world.geojson"),
promiseWrapper(d3.json, "militaryBases.geojson")])
.then(resolve => {
createMap(resolve[0], resolve[1])
})
function createMap(countries,bases){
var projection = d3.geoOrthographic()
.scale(scale)
.translate([width/2,height/2])
var geoPath = d3.geoPath()
.projection(projection);
// SET UP ZOOM //
var zoomSettings = d3.zoomIdentity
.translate(0,0) // Start out untranslated
.scale(scale)
var rotateScaleY = d3.scaleLinear() // This will be used to transform the x-zoom attribute (which ranges from -500 to 500) into degrees.
.domain([-500,0,500])
.range([180,0,-180])
var rotateScaleX = d3.scaleLinear() // This will be used to transform the x-zoom attribute (which ranges from -500 to 500) into degrees.
.domain([-250,0,250])
.range([-180,0,180])
var xRotate = 0
var yRotate = 0
var zoom = d3.zoom()
.on("zoom", rotate)
function rotate() {
var e = d3.event
var rotateScale = projection.scale()/scale
var xRotate = rotateScaleX(e.transform.x) % 360 // Gives you the angle of rotation
var yRotate = rotateScaleY(e.transform.y) % 360
projection
.rotate([xRotate,yRotate])
.scale(projection.scale())
// console.log(xRotate,yRotate)
//Redraw the paths + graticules + cities with the updated projection.
d3.selectAll("path.graticule").attr("d",geoPath)
d3.selectAll("path.countries").attr("d", geoPath)
d3.selectAll("circle.bases")
.each(function (d, i) {
var projectedPoint = projection([d.geometry.coordinates[0],d.geometry.coordinates[1]])
var x = parseInt(d.geometry.coordinates[0])
var y = parseInt(d.geometry.coordinates[1])
var display = (x + xRotate < 90 && x + xRotate > -90
|| (x + xRotate < -270 && x + xRotate > -450)
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90
|| (y + yRotate < -270 && y + yRotate > -450)
|| (y + yRotate > 270 && y + yRotate < 450)
? "block" : "none";
d3.select(this)
.attr("cx", projectedPoint[0])
.attr("cy", projectedPoint[1])
.style("display", display)
})
}
d3.select("#Chart").append("button").on("click", () => { zoomButton("in")}).html("Zoom In").attr("id","in")
d3.select("#Chart").append("button").on("click", () => { zoomButton("out")}).html("Zoom Out").attr("id","out")
function zoomButton(zoomDirection){
// Get the current rotation + zoom
xRotate = projection.rotate()[0]
yRotate = projection.rotate()[1]
var backgroundZoom = 1;
if(zoomDirection == "in"){
// Increase the scale
var newZoom = projection.scale() * 1.5;
backgroundZoom = 1.5
}
else if(zoomDirection = "out"){
// Decrease the scale
var newZoom = projection.scale() * .75;
backgroundZoom = .75
}
projection.scale(newZoom)
d3.selectAll("path.graticule").transition().duration(250).attr("d",geoPath)
d3.selectAll("path.countries").transition().duration(250).attr("d", geoPath)
d3.selectAll("circle.bases")
.each(function (d, i) {
var projectedPoint = projection([d.geometry.coordinates[0],d.geometry.coordinates[1]])
var x = parseInt(d.geometry.coordinates[0])
var y = parseInt(d.geometry.coordinates[1])
var display = (x + xRotate < 90 && x + xRotate > -90
|| (x + xRotate < -270 && x + xRotate > -450)
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90
|| (y + yRotate < -270 && y + yRotate > -450)
|| (y + yRotate > 270 && y + yRotate < 450)
? "block" : "none";
d3.select(this)
.transition().duration(250)
.attr("cx", projectedPoint[0])
.attr("cy", projectedPoint[1])
.style("display", display)
})
var currentR = d3.select("#backgroundCircle").attr("r")
console.log(newZoom/scale,currentR)
d3.select("#backgroundCircle")
.transition().duration(250)
.attr("r", function(){
return currentR * backgroundZoom;
})
}
// DRAW PATHS
d3.select("svg")
.call(zoom)
.call(zoom.transform, zoomSettings)
.on("wheel.zoom",null)
.on("dblclick.zoom",null)
g.selectAll("path").data(countries.features)
.enter()
.append("path")
.attr("class", "countries")
.attr("d", geoPath)
g.selectAll("circle").data(bases)
.enter()
.append("circle")
.attr("class", "bases")
.attr("r", 2)
.attr("cx", d => projection([d.geometry.coordinates][0],d.geometry.coordinates[1])[0])
.attr("cy", d => projection([d.geometry.coordinates][0],d.geometry.coordinates[1])[1])
.style("display", function(d){
var x = parseInt(d.geometry.coordinates[0])
var y = parseInt(d.geometry.coordinates[1])
var display = (x + xRotate < 90 && x + xRotate > -90
|| (x + xRotate < -270 && x + xRotate > -450)
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90
|| (y + yRotate < -270 && y + yRotate > -450)
|| (y + yRotate > 270 && y + yRotate < 450)
? "block" : "none";
return display
})
.on("mouseover", displayName)
.on("mouseout", hideName)
var tooltip = d3.select("body").append("div")
.attr("id","tooltip")
.classed("showing",true)
.style("opacity",0)
function displayName(d){
tooltip.text(d.properties.name)
var coordinates = [0,0]
coordinates = d3.mouse(this);
var x = coordinates[0]
var y = coordinates[1]
tooltip.style("left",x + 20 + "px")
tooltip.style("top",y + 30 + "px")
d3.select("#tooltip").transition().duration(250).style("opacity",1)
}
function hideName(){
d3.select("#tooltip").transition().duration(250).style("opacity",0)
}
g.insert("circle","path")
.attr("id","backgroundCircle")
.attr("r",scale)
.attr("cx",width/2)
.attr("cy", height/2)
}
</script>
</footer>
</html>
https://d3js.org/d3.v4.min.js