Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active December 7, 2016 14:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HarryStevens/1c953744582a7b50d77f79d206dbfbc6 to your computer and use it in GitHub Desktop.
Save HarryStevens/1c953744582a7b50d77f79d206dbfbc6 to your computer and use it in GitHub Desktop.
Turn the Earth
license: gpl-3.0

Use the dials on the left to adjust the rotation of a geographic projection. D3 supports three-axis rotation, allowing you to adjust the lambda (λ), phi (φ) and gamma (γ) axes.

This map uses an orthographic projection with D3.js and Topojson to draw a map of the world's countries onto a globe, rendered here with a graticule that is 10° by 10°.

The world countries polygons were downloaded from ArcGIS.

Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: "Helvetica Neue", sans-serif;
margin: 0;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var width = window.innerWidth, height = window.innerHeight;
var projection = d3.geoOrthographic()
.scale(width / 4.1)
.translate([width / 2, height / 2])
.clipAngle(90 + 1e-6)
.precision(1)
.rotate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var graticule = d3.geoGraticule()
.step([10, 10]);
g.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path)
.style("fill", "#fff")
.style("stroke", "#ccc");
var dials = [{
name: "λ",
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([-180, 180]),
rscale: d3.scaleLinear().domain([-180, 180]).range([40, height / 2 - 40])
},{
name: "φ",
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([90, -90]),
rscale: d3.scaleLinear().domain([90, -90]).range([40, height / 2 - 40])
},{
name: "γ",
scale: d3.scaleLinear().domain([40, height / 2 - 40]).range([180, -180]),
rscale: d3.scaleLinear().domain([180, -180]).range([40, height / 2 - 40])
}];
svg.selectAll(".dial-rect")
.data(dials)
.enter().append("rect")
.attr("class", "dial-rect")
.attr("x", function(d,i){ return 45 * (i + 1); })
.attr("y", 40)
.attr("width", 20)
.attr("height", height / 2 - 80)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#ccc")
.style("fill", "#3a403d");
svg.selectAll(".dial-text")
.data(dials)
.enter().append("text")
.attr("class", "dial-text")
.attr("x", function(d,i) { return 45 * (i + 1); })
.attr("y", 15)
.attr("dx", 10)
.attr("text-anchor", "middle")
.style("fill", "#3a403d")
.text(function(d){ return d.name; });
svg.selectAll(".dial-circle")
.data(dials)
.enter().append("circle")
.attr("class", function(d){ return "dial-circle dial-" + d.name; })
.attr("cx", function(d,i){ return 45 * (i + 1) + 10; })
.attr("cy", function(d){ return d.rscale(0); })
.attr("r", 20)
.style("stroke", "#aaa")
.style("fill", "#ccc")
.style("cursor", "ns-resize")
.call(d3.drag().on("drag", dragged));
svg.selectAll(".dial-circle-text")
.data(dials)
.enter().append("text")
.attr("class", function(d) { return "dial-circle-text dial-" + d.name; })
.attr("x", function(d,i){ return 45 * (i + 1) + 10; })
.attr("y", function(d){ return d.rscale(0) + 5; })
.attr("text-anchor", "middle")
.style("font-size", ".7em")
.style("cursor", "ns-resize")
.text("0")
.call(d3.drag().on("drag", dragged));
function dragged(d){
var y = d3.mouse(this)[1];
y < 40 ? y = 40 : y = y;
y > height / 2 - 40 ? y = height / 2 - 40 : y = y;
d3.select(".dial-circle.dial-" + d.name)
.attr("cy", y);
d3.select(".dial-circle-text.dial-" + d.name)
.attr("y", y + 5)
.text(Math.round(d.scale(y)));
projection.rotate([dials[0].scale(d3.select(".dial-λ").attr("cy")), dials[1].scale(d3.select(".dial-φ").attr("cy")), dials[2].scale(d3.select(".dial-γ").attr("cy"))])
g.selectAll("path").attr("d", path);
}
var c = d3.scaleOrdinal(d3.schemeCategory20);
d3.json("countries.json", function(error, data){
g.selectAll(".subunit")
.data(topojson.feature(data, data.objects.polygons).features)
.enter().append("path")
.attr("class", "subunit")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1px")
.style("fill", function(d,i){ return c(i); })
.style("opacity", ".6");
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment