|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<style> |
|
body { |
|
margin: 0; |
|
} |
|
#rotation { |
|
position: absolute; |
|
font-family: monospace; |
|
padding: 10px; |
|
background: rgba(255, 255, 255, .5); |
|
} |
|
#rotation input { |
|
width: 300px; |
|
} |
|
.graticule { |
|
fill: none; |
|
stroke: #ccc; |
|
} |
|
.country { |
|
stroke: #fff; |
|
} |
|
line { |
|
stroke: tomato; |
|
stroke-width: 3px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="rotation"></div> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://unpkg.com/[email protected]/dist/topojson.js"></script> |
|
<script src="https://unpkg.com/[email protected]/build/versor.min.js"></script> |
|
<script src="https://unpkg.com/[email protected]/build/d3-inertia.min.js"></script> |
|
<script> |
|
var angles = ["λ", "φ", "γ"]; |
|
|
|
angles.forEach(function(angle, index){ |
|
d3.select("#rotation").append("div") |
|
.attr("class", "angle-label angle-label-" + index) |
|
.html(angle + ": <span>0</span>") |
|
|
|
d3.select("#rotation").append("input") |
|
.attr("type", "range") |
|
.attr("class", "angle angle-" + index) |
|
.attr("min", "-180") |
|
.attr("max", "180") |
|
.attr("value", "0"); |
|
}); |
|
|
|
var width = window.innerWidth, height = window.innerHeight; |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var projection = d3.geoOrthographic() |
|
.scale(d3.min([width / 2, height / 2])) |
|
.translate([width / 2, height / 2]) |
|
.precision(1); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
// append the graticule |
|
svg.append("path") |
|
.datum(d3.geoGraticule().step([10, 10])) |
|
.attr("class", "graticule") |
|
.attr("d", path); |
|
|
|
var line = svg.append("line"), |
|
pts; |
|
|
|
// inertia versor dragging |
|
var inertia = d3.geoInertiaDrag(svg, function() { render(); }, projection); |
|
|
|
function render(){ |
|
if (inertia.t){ |
|
// make the line |
|
pts = [ |
|
[inertia.position[0] + inertia.velocity[0] / 10, inertia.position[1] + inertia.velocity[1] / 10], |
|
[inertia.position[0] + inertia.velocity[0] * inertia.t / 10, inertia.position[1] + inertia.velocity[1] * inertia.t / 10] |
|
]; |
|
|
|
line |
|
.attr("x1", pts[0][0]) |
|
.attr("y1", pts[0][1]) |
|
.attr("x2", pts[1][0]) |
|
.attr("y2", pts[1][1]); |
|
} |
|
|
|
update(projection.rotate()); |
|
} |
|
|
|
d3.selectAll("input").on("input", function(){ |
|
var p = []; |
|
d3.selectAll("input").each(function(d, i){ |
|
p.push(+d3.select(this).property("value")); |
|
}); |
|
|
|
if (inertia.timer) { |
|
inertia.timer.stop(); |
|
removeLine(); |
|
} |
|
|
|
update(p); |
|
}); |
|
|
|
// this fixes a strange behavior, where when you click on the svg while the globe is moving, |
|
// the movement stops but the line remains |
|
svg.on("click", function(){ |
|
removeLine(); |
|
}); |
|
|
|
function update(eulerAngles){ |
|
angles.forEach(function(angle, index){ |
|
d3.select(".angle-label-" + index + " span").html(Math.round(eulerAngles[index])) |
|
d3.select(".angle-" + index).property("value", eulerAngles[index]) |
|
}); |
|
|
|
projection.rotate(eulerAngles); |
|
svg.selectAll("path").attr("d", path); |
|
} |
|
|
|
function removeLine(){ |
|
line.attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", 0); |
|
} |
|
|
|
d3.json("countries.json", function(error, countries){ |
|
if (error) throw error; |
|
|
|
svg.selectAll(".country") |
|
.data(topojson.feature(countries, countries.objects.polygons).features) |
|
.enter().append("path") |
|
.attr("class", "country") |
|
.attr("d", path); |
|
}); |
|
</script> |
|
</body> |
|
</html> |