|
<!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> |