Skip to content

Instantly share code, notes, and snippets.

@dcposch
Created November 25, 2012 05:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcposch/4142482 to your computer and use it in GitHub Desktop.
Save dcposch/4142482 to your computer and use it in GitHub Desktop.
D3GL Space Exploration

All soft landings accomplished by humans so far on celestial bodies. Click on the year to play back. Drag to rotate, scroll to zoom. Click any landing site for details. Zoom out for transparency, zoom in to make the planets and moons opaque. Red represent Soviet spacecraft, blue represents NASA spacecraft.

We'll add a third color when Elon Musk lands on Mars.

Built with d3gl

<!DOCTYPE html>
<html>
<head>
<title>D3GL Space</title>
<link rel="stylesheet" href="http://dcpos.ch/d3gl/css/demo.css" />
</head>
<style>
body{
background:black;
color:white;
}
h1 {
width:300px;
}
h1,h2,div{
margin:10px;
}
#labels,#globes {
margin-left:0;
margin-right:0;
}
#year{
position:absolute;
top:300px;
left:660px;
}
#labels span {
display:inline-block;
width:240px;
text-align:center;
}
</style>
<body>
<script type="text/javascript" src="http://dcpos.ch/d3gl/js/d3gl.min.js"></script>
<h1 id="year"></h1>
<div id="globes"></div>
<div id="labels"></div>
<div id="detail"></div>
<script type="text/javascript">
$(function(){
// all celestial bodies humans have landed on
var bodies = [
"moon",
"venus",
"mars",
"titan"
];
// make four globes
d3.select("#labels").selectAll("span")
.data(bodies).enter().append("span")
.text(function(body){ return body; });
var globe = d3.gl.globe()
.width(240).height(240)
.texture(function(body){return "http://dcpos.ch/d3gl/img/"+body+"-tex.jpg";})
.transparency(function(body){ return Math.max(1.25 - globe.zoom()/2, 0.5); }); // zoom in -> opaque, zoom out -> glassy
// now, render all the landings onto the globes
d3.csv("http://dcpos.ch/d3gl/data/space.csv", function(rows){
// organize the data
var landings = {};
rows.forEach(function(r){
r.site_lat = parseFloat(r.site_lat);
r.site_lon = parseFloat(r.site_lon);
var p = r.land_date.split("-");
r.land_date = new Date(parseInt(p[0]), parseInt(p[1]), parseInt(p[2]));
if(!landings[r.body])landings[r.body]=[];
landings[r.body].push(r);
});
// view model
var minDate, maxDate, currentDate;
minDate = new Date(1962, 1, 1);
maxDate = currentDate = new Date();
var latestLanding = null;
var playing = false; // true during playback
var mouseoverPoint; // highlight on mouseover, click to view details
// render the landings
globe.points()
.data(function(body){ return landings[body]; })
.latitude(function(d){ return d.site_lat; })
.longitude(function(d){ return d.site_lon; })
.radius(function(d){
var dt = currentDate.getTime()-d.land_date.getTime();
dt /= 1000*60*60*24; // how many days ago we landed
if(dt < 0) return 0; // hasn't happened yet.
else return Math.max(4 / (1 + dt/500), 1.5); // shrink old landings
})
.color(function(d){
if(d == mouseoverPoint) return "#fff"; // highlight
var russia = "#c00", usa = "#33b";
if(d.name.indexOf("Luna") >= 0) return russia;
if(d.name.indexOf("Venera") >= 0) return russia;
if(d.name.indexOf("Vega") >= 0) return russia;
return usa;
})
.on('mousemove', function(evt){
mouseoverPoint = evt.point;
})
.on('click', function(evt){
if(evt.point) showDetails(evt.point);
});
d3.select("#globes").selectAll("span")
.data(bodies).enter().append("span").call(globe);
// click the year label to start playback
$("#year").mousedown(function(){
playing = !playing;
if(playing && currentDate.getTime()>=maxDate.getTime()) currentDate = minDate; // start over
}).css("cursor", "pointer");
// animate during playback
globe.on("update", function(){
// this event fires 60 times per sec, once before each render
if(playing) currentDate = new Date(currentDate.getTime() + 1000*60*60*24*4); // add four days
if(currentDate.getTime() >= maxDate.getTime()) playing = false; // playback finished
updateYearDisplay(currentDate);
// highlight the most recent landing
var ll = getLatestLanding(rows, currentDate);
if(!ll || ll == latestLanding) return; // no change...
showDetails(ll);
globe.rotateTo([ll.site_lat, ll.site_lon]);
latestLanding = ll;
});
});
// helper functions
function getLatestLanding(landings, date){
var most_recent, ll = null;
landings.forEach(function(row){
var ago = date.getTime() - row.land_date.getTime();
if(ago < 0) return;
if(most_recent && most_recent < ago) return;
most_recent = ago;
ll = row;
});
return ll;
}
function updateYearDisplay(d){
$("#year").text(dateToStr(d, false));
}
function dateToStr(d, days){ // returns eg "2012 Nov" or "2012 Nov 30"
var str = d.getFullYear() + " " +
["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"][d.getMonth()];
if(days) str += " "+d.getDate();
return str;
}
function showDetails(landing){
var body = landing.body=="moon"?
"the moon":
landing.body[0].toUpperCase()+landing.body.substring(1);
var coords =
Math.abs(landing.site_lat) + (landing.site_lat>0?"N":"S")+" "+
Math.abs(landing.site_lon) + (landing.site_lon>0?"E":"W");
var htm = "<h2>"+landing.name+"</h2>"+
"<div>"+landing.site_name+" "+coords+"</div>"+
"<div>landed "+dateToStr(landing.land_date,true)+" on "+body+"</div>";
$("#detail").html(htm);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment