Quick visualisation using D3.js to show location of US Foreign Military bases and facilities. Data from David Vine
Special thanks to fil for helping me properly project the points.
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://unpkg.com/versor"></script>
<script src="https://unpkg.com/d3-inertia"></script>
<style>
body { margin:0;
position:fixed;
top:0;
right:0;
bottom:0;
left:0;
background: #fcfcfa;
font-family: "Roboto", sans-serif;
}
main {
width: 25vw;
margin: 0 auto;
padding: 5vw;
max-width: 50em;
}
circle:hover {
stroke: #002147;
}
.container {
display: flex; /* or inline-flex */
}
.world {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
}
</style>
</head>
<body>
<div class="container">
<svg>
<defs>
<!-- <pattern id="usflag" x="0" y="0" width="50" height="25" patternUnits="objectBoundingBox">
<image xlink:href="usflag.png" x="-15" y="-5" width="50" height="25"></image>
</pattern>
-->
<!-- <pattern id="usflag" x="0" y="0" width="25" height="25" patternUnits="userSpaceOnUse">
<rect width="10" height="3" fill="#BF0A30"/>
<rect width="10" height="4" fill="#ffffff"/>
<rect width="10" height="3" fill="#BF0A30"/>
</pattern>
-->
<linearGradient id="gradBlue" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#005C99;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0099FF;stop-opacity:1" />
</linearGradient>
<radialGradient id="gradient" cx="75%" cy="25%">
<stop offset="5%" stop-color="#ffd"></stop><stop offset="100%" stop-color="#ba9"></stop>
</radialGradient>
</defs>
</svg>
<main>
<h1></h1>
<h2>Click on a US Flag!</h2>
<p id="notes">
</p>
<p>Data from David Vine"s research for <a href="https://www.davidvine.net/base-nation.html" target="_blank">Base Nation book</a></p>
</main>
</div>
<script>
var title = d3.select("h1");
var notesP = d3.select("#notes");
var notes = d3.select("h2");
var width = 960,
height = 700,
margin = { top: 10, right: 10, bottom: 10, left: 10 },
originalScale = height / 2.0,
scale = originalScale,
translation = [width / 2, height / 2],
scaleChange,
rotation;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.top + ", " + margin.left + ")");
var sphere = {type: "Sphere"};
var graticule = d3.geoGraticule();
var projection = d3.geoOrthographic()
.scale(scale)
.translate(translation)
.clipAngle(90);
var path = d3.geoPath()
.projection(projection);
d3.queue()
.defer(d3.csv, "bases.csv")
.defer(d3.csv, "lilypads.csv")
.defer(d3.csv, "usfunded.csv")
.defer(d3.json, "https://unpkg.com/world-atlas@1/world/110m.json")
.await(load);
function load(error, bases, lilypads, usfunded, world,) {
if (error) throw error;
var countries = topojson.feature(world, world.objects.countries).features;
grid = graticule();
svg.append("g")
.attr("id", "globegroup")
.append("path")
.datum(sphere)
.attr("class", "world")
.attr("id", "sphere")
.attr("fill", "url(#gradBlue)")
.attr("stroke", "#ccc")
.attr("stroke-width", 1)
.attr("z-index", 100);
svg.selectAll(".grid")
.data([grid])
.enter()
.append("path")
.classed("world", true)
.classed("grid", true)
.attr("fill", "none")
.attr("stroke", "#ffd")
.attr("stroke-width", 1);
svg.selectAll(".country")
.data(countries)
.enter().insert("path")
.attr("class", "countries")
.attr("class", "world")
.attr("id", function (d) { return d.id; })
.attr("fill", "#ccc")
.attr("stroke", "#fff")
.append("title")
.text(function(d) { console.log(d); }); //return countrycodesDict[d.id];
svg.selectAll(".lilypad")
.data(lilypads)
.enter().append("circle")
.attr("fill", "red")
.attr("r", 4)
.append("title")
.text(function(d) { return "Lilypad: " + d.name });
svg.selectAll(".usfunded")
.data(usfunded)
.enter().append("circle")
.attr("fill", "red")
.attr("r", 2.5)
.append("title")
.text(function(d) { return "US Funded: " + d.name });
svg.selectAll(".base")
.data(bases)
.enter().append("circle")
.attr("stroke", "red")
.attr("fill", "#fff")
.attr("r", 5.5)
.append("title")
.text(function(d) { return "Base: " + d.name });
svg.select("#USA")
.attr("stroke", "#000")
.attr("stroke-width", 2);
svg.selectAll("circle")
.on("click", function(d) {
title.html(d.name + ", " + d.country);
notes.html("Notes");
notesP.html(d.notes);
})
reproject();
// Zoom and pan set-up
var zoom = d3.zoom()
.scaleExtent([0.5, 4])
.on("zoom", zoomed)
d3.select("svg").call(zoom);
var previousScaleFactor = 1;
function zoomed() {
var dx = d3.event.sourceEvent.movementX;
var dy = d3.event.sourceEvent.movementY;
var event = d3.event.sourceEvent.type;
if (event === "wheel") {
scaleFactor = d3.event.transform.k;
scaleChange = scaleFactor - previousScaleFactor;
scale = scale + scaleChange * originalScale;
projection.scale(scale);
previousScaleFactor = scaleFactor;
}
reproject();
} // zoomed()
} // load()
d3.geoInertiaDrag(svg, reproject);
function reproject() {
var c = projection.rotate().slice(0,2).map(d => -d)
d3.selectAll("circle")
.attr("transform", function(d) { return "translate(" + projection([d.lon,d.lat]) + ")";})
.attr("opacity", function(d){
// clipAngle(90)
return d3.geoDistance([+d.lon,+d.lat], c) < Math.PI/2 ? 1 : 0;
})
d3.selectAll(".world")
.attr("d", path);
}
</script>
https://d3js.org/d3.v4.min.js
https://d3js.org/topojson.v2.min.js
https://unpkg.com/versor
https://unpkg.com/d3-inertia