Skip to content

Instantly share code, notes, and snippets.

@Mavromatika
Last active October 23, 2019 22:50
Show Gist options
  • Save Mavromatika/cc5b26d140e197f55f9e to your computer and use it in GitHub Desktop.
Save Mavromatika/cc5b26d140e197f55f9e to your computer and use it in GitHub Desktop.
Exploring the Solar System
[
{"planet":"Mercury","radius":2439.7,"moons":[],"nmoons":0},
{"planet":"Venus","radius":6051.8,"moons":[],"nmoons":0},
{"planet":"Earth","radius":6371.0,"moons":[{"moon":"Moon","radius":1737.5}],"nmoons":1},
{"planet":"Mars","radius":3389.5,"moons":[{"moon":"Phobos","radius":11.1},{"moon":"Deimos","radius":6.2}],"nmoons":2},
{"planet":"Ceres","radius":476.2,"moons":[],"nmoons":0},
{"planet":"Jupiter","radius":69911,"moons":[{"moon":"Io","radius":1821.6},{"moon":"Europa","radius":1560.8},{"moon":"Ganymede","radius":2631.2},{"moon":"Callisto","radius":2410.3}],"nmoons":67},
{"planet":"Saturn","radius":58232,"moons":[{"moon":"Titan","radius":2574.7},{"moon":"Iapetus","radius":735.6},{"moon":"Rhea","radius":764.3},{"moon":"Dione","radius":561.7},{"moon":"Tethys","radius":533.0}],"nmoons":62},
{"planet":"Uranus","radius":25362,"moons":[{"moon":"Oberon","radius":761.4},{"moon":"Titania","radius":788.9},{"moon":"Ariel","radius":578.9},{"moon":"Umbriel","radius":584.7}],"nmoons":27},
{"planet":"Neptune","radius":24622,"moons":[{"moon":"Triton","radius":1353.4}],"nmoons":14},
{"planet":"Pluto","radius":1151,"moons":[{"moon":"Charon","radius":603.6}],"nmoons":5},
{"planet":"Eris","radius":0,"moons":[{"moon":"Dysnomia","radius":0}],"nmoons":1},
{"planet":"Haumea","radius":0,"moons":[{"moon":"Namaka","radius":0},{"moon":"Hi'iaka","radius":0}],"nmoons":2},
{"planet":"Makemake","radius":0,"moons":[],"nmoons":0}
]
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background: #000000;
font-family: sans-serif;
}
h1, h2 {
color: #eaeaea;
text-align: center;
}
.first-text {
color: #eaeaea;
font-size: 80%;
width: 300px;
text-align: justify;
margin-left: auto;
margin-right: auto;
margin-bottom: 60px;
}
.source {
font-size: 70%;
}
.source a {
color: #eaeaea;
}
.schema {
width: 1300px;
margin-left: auto;
margin-right: auto;
margin-bottom: 60px;
}
.planet{
width: 1200px;
position: relative;
margin-left: auto;
margin-right: auto;
}
.svgPlanet{
position: absolute;
top: 0;
left: 50%;
/*padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;*/
}
canvas {
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<!--<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>-->
<h1>Exploring the Solar System</h1>
<h2>The planets and their moons</h2>
<div class="first-text">
<p>The graphic below shows the known planets of the Solar System and their main satellites (radius > 500km). Their sizes are proportional, except for the smallest objects (r ~ 500 km).</p>
<p class="source">Source : <a href="https://solarsystem.nasa.gov/planets/solarsystem">NASA</a></p>
</div>
<div class="schema"></div>
<h2>Where probes and landers have landed</h2>
<div class="first-text">
<p>Hi-resolution images from NASA were projected on the globes below. The places where landers or probes have reached the surface (by landing or crashing...) are represented by dots.</p>
<p>The globes can be rotated by dragging them.</p>
<p class="source">Sources : NASA for <a href="http://nssdc.gsfc.nasa.gov/planetary">landing coordinates</a>, and for the maps of <a href="http://maps.jpl.nasa.gov/venus.html">Venus</a>, <a href="http://www.mars.asu.edu/data/mdim_color/">Mars</a>, and the <a href="http://astrogeology.usgs.gov/search/details/Moon/Clementine/UVVIS/Lunar_Clementine_UVVIS_750nm_Global_Mosaic_118m_v2/cub">Moon</a>.</p>
</div>
<div id="content"></div>
<script>
var width = 1200, height = 600; // of the images (for the looping through pixels)
var W = 960, H = 560; // of the planet containers
var schemaW = 1300, schemaH = 300; // of the schema container for the size comparison
var landingR = 4;
var projScale =250;
var labelSpacing = 40;
var padL = 160, padR = 160, padB = 0, paddingT = 30;
var projection = d3.geo.orthographic().scale(projScale).translate([W/2,H/2]);
var compXScale = d3.scale.ordinal().range([0,schemaW]);
var compRScale = d3.scale.linear().range([1,schemaH/2]);
var λ = d3.scale.linear()
.domain([0, W])
.range([-180, 180]);
var drag = d3.behavior.drag().on("drag",dragmove).on("dragend",dragend);
var schemaContainer = d3.select(".schema");
var svgSchema = schemaContainer.append("svg").attr("width",schemaW).attr("height",schemaH);
// A function called when images load, with the image passed as parameter.
var onload = function(pic) {
return function(){
pic.dataImage.dx = pic.width,
pic.dataImage.dy = pic.height;
pic.canvas.attr("width",pic.dataImage.dx).attr("height",pic.dataImage.dy);
pic.context.drawImage(pic, 0, 0, pic.dataImage.dx, pic.dataImage.dy); // Draw the image once on canvas
pic.dataImage.sourceData = pic.context.getImageData(0, 0, pic.dataImage.dx, pic.dataImage.dy).data, // get the data (pixels)
pic.dataImage.target = pic.context.createImageData(pic.dataImage.dx, pic.dataImage.dy), // create a new, empty image on the canvas
pic.dataImage.targetData = pic.dataImage.target.data;
pic.canvas.attr("width", W).attr("height",H);
display(pic, 0);
}
}
// Create all the images of the planets
var sources = [{"name":"Mars","src":"mars-small.jpg","landings":[{"id":0,"mission":"Viking 1","coord":[-49.97,22.48]},
{"id":1,"mission":"Viking 2","coord":[-225.74,47.97]},
{"id":2,"mission":"Mars 2","coord":[47,-45]},
{"id":3,"mission":"Mars 3","coord":[-158,-45]},
{"id":4,"mission":"Mars 6","coord":[-19.42,-23.90]},
{"id":5,"mission":"Mars Pathfinder","coord":[-33.55,19.33]},
{"id":6,"mission":"Spirit","coord":[175.478,-14.572]},
{"id":7,"mission":"Opportunity","coord":[5.527,-1.946]},
{"id":8,"mission":"Phoenix Mars Lander","coord":[-125.9,68.15]},
{"id":9,"mission":"Curiosity","coord":[137.4,-4.5]},
]},
//{"name":"Jupiter","src":"jupiter-small.jpg"},
{"name":"Venus","src":"venus-small.jpg","landings":[{"id":0,"mission":"Pioneer Large Probe","coord":[-56,4.4]},
{"id":1,"mission":"Pioneer North Probe","coord":[4.8,59.3]},
{"id":2,"mission":"Pioneer Day Probe","coord":[-43,-31.3]},
{"id":3,"mission":"Pioneer Night Probe","coord":[56.7,28.7]},
{"id":4,"mission":"Venera 4","coord":[38,19]},
{"id":5,"mission":"Venera 5","coord":[18,-3]},
{"id":6,"mission":"Venera 6","coord":[23,-5]},
{"id":7,"mission":"Venera 7","coord":[-9,-5]},
{"id":8,"mission":"Venera 8","coord":[-25,-10]},
{"id":9,"mission":"Venera 9","coord":[-69,31.7]},
{"id":10,"mission":"Venera 10","coord":[-69,16]},
{"id":11,"mission":"Venera 11","coord":[-61,-14]},
{"id":12,"mission":"Venera 12","coord":[-66,-7]},
{"id":13,"mission":"Venera 13","coord":[-57,-7.5]},
{"id":14,"mission":"Venera 14","coord":[-50,-13.15]}
]},
{"name":"Moon","src":"moon-small.jpg","landings":[{"id":0,"mission":"Apollo 11","coord":[23.47298,0.67409]},
{"id":1,"mission":"Apollo 12","coord":[-23.41930,-3.01381]},
{"id":2,"mission":"Apollo 14","coord":[-17.47139,-3.64544]},
{"id":3,"mission":"Apollo 15","coord":[3.63400,26.13224]},
{"id":4,"mission":"Apollo 16","coord":[15.49859,-8.97341]},
{"id":5,"mission":"Apollo 17","coord":[30.77475,20.18809]},
]}
//{"name":"Earth","src":"earth-small.jpg"},
//{"name":"Mercury","src":"mercury-small.jpg"}, // No landings
//{"name":"Saturn","src":"saturn-small.jpg"}
];
var images = [];
// Create all the image objects from the data in "sources"
for (var i = 0; i < sources.length; i++){
var cont = d3.select("#content").append("div").attr("class","planet");
var canv = cont.append("canvas");
images.push(new Image);
var it = images[i];
it.container = cont;
it.canvas = canv;
it.context = canv.node().getContext("2d");
it.dataImage = {sourceData : [], target : Object, targetData : [], dx : 0, dy : 0};
it.svg = cont.append("svg").attr("width",W).attr("height",H).attr("class","svgPlanet").style("margin-left", "-" + (W/2).toString() + "px");
if (sources[i].landings){
it.landings = sources[i].landings;
}
it.angle = 0;
it.onload = onload(images[i]);
it.src = sources[i].src;
// A title, and a transparent circle on top to handle drag events.
it.svg.append("text").attr("x",W/2).attr("y",H-10).text(sources[i].name).attr("fill","#eaeaea").attr("text-anchor","middle");
it.svg.append("circle").attr("cx",W/2).attr("cy",H/2)
.attr("r",projScale)
.attr("fill","transparent")
.attr("class","circdrag")
.attr("id","i" + i.toString());
// Position points if landings exist.
if (it.landings){
it.groups = it.svg.selectAll(".landings")
.data(it.landings, function(d){return d.id;}) // Necessary to keep track of el when sorting
.enter()
.append("g")
.attr("class","landings");
it.circles = it.groups.append("circle").attr("class","circland")
.attr("cx", function(d){return projection(d.coord)[0];})
.attr("cy", function(d){return projection(d.coord)[1];})
.attr("r",landingR)
.attr("fill","#eaeaea")
.attr("stroke","black")
.attr("title",function(d){return d.coord[0].toString() + " , " + d.coord[1].toString();});
it.lines = it.groups.append("line")
.attr("class","lineland")
.attr("x1",padL)
.attr("y1",0)
.attr("x2",function(d){return projection(d.coord)[0];})
.attr("y2",function(d){return projection(d.coord)[1];})
.attr("stroke","#eaeaea");
it.labels = it.groups.append("text")
.attr("fill","#eaeaea")
.text(function(d){return d.mission;});
}
}
// Create an array with only the visible points given the projection (to loop through later).
var toLoop = [], k = -1;
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var e = projection.invert([x,y]);
if (!isNaN(e[0])) {
toLoop.push([e[0],e[1],k]);
}
k += 4;
}
}
//---------------------------------------------------------------------------------------------------------------------------------------
// Schema of the planets for size comparison
d3.json("datasize.json", function(error, data){
if (error) return console.warn(error);
compRScale.domain([d3.min(data, function(d){return +d.radius;}),
d3.max(data, function(d){return +d.radius;})]);
// Compute the space between planets and their position on x axis.
var sum = 0;
for (i = 0;i < data.length; i++){
sum += compRScale(data[i].radius);
}
var space = ((schemaW-30) - (sum*2)) / (data.length+1);
var spaceArray = [], memo = 0;
for (i = 0;i < data.length; i++){
memo = memo + space + compRScale(data[i].radius);
spaceArray.push(memo);
memo += compRScale(data[i].radius);
}
var dataMoons = [];
for (i=0;i<data.length;i++){
if (data[i].nmoons!=0){
for (j=0;j<data[i].moons.length;j++){
dataMoons.push({"index" : i, "ypos" : j, "data" : data[i].moons[j]});
}
}
}
var plaComp = svgSchema.selectAll(".planetComparison")
.data(data)
.enter()
.append("circle");
var txtComp = svgSchema.selectAll(".textComparison")
.data(data)
.enter()
.append("text");
var satComp = svgSchema.selectAll(".satComparison")
.data(dataMoons)
.enter()
.append("circle");
var txtSatComp = svgSchema.selectAll(".textSatComparison")
.data(dataMoons)
.enter()
.append("text");
plaComp.attr("class","planetComparison")
.attr("cx",function(d,i){return spaceArray[i];})
.attr("cy",150)
.attr("r",function(d){return compRScale(+d.radius);})
.attr("stroke","#a3a3a3");
satComp.attr("class","satComparison")
.attr("cx",function(d,i){return spaceArray[d.index];})
.attr("cy",function(d){return 180 + d.ypos*20;})
.attr("r",function(d){return compRScale(+d.data.radius);})
.attr("stroke","#a3a3a3");
txtComp.attr("class","textComparison")
.attr("x",function(d,i){return spaceArray[i];})
.attr("y",schemaH/2 - 15)
.attr("fill","white")
.attr("text-anchor","start")
.attr("transform",function(d,i){
return "rotate(-40 " + spaceArray[i].toString() + " " + (schemaH/2 - 15).toString() + ")";
})
.text(function(d){return d.planet;});
txtSatComp.attr("class","textSatComparison")
.attr("x",function(d,i){return spaceArray[d.index] - 10;})
.attr("y",function(d){return 180 + d.ypos*20;})
.attr("fill","white")
.attr("text-anchor","end")
.attr("transform",function(d,i){
return "rotate(-40 " + spaceArray[d.index].toString() + " " + (180 + d.ypos*20).toString() + ")";
})
.style("font-size","12px")
.text(function(d){return d.data.moon;});
});
//-----------------------------------------------------------------------------------------------------------------------------
// Function that displays the planets.
function display(image, angle){
for (var j = 0; j < toLoop.length; ++j){
var λ = toLoop[j][0], φ = toLoop[j][1];
var i = toLoop[j][2];
// Equivalent to : (numRow * width + numCol ) * 2^2 , or : numRow * width * 4 + numCol * 4.
// See here : https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
var q = ((90 - φ) / 180 * image.dataImage.dy | 0) * image.dataImage.dx + ((180 + λ + angle) / 360 * image.dataImage.dx | 0) << 2;
image.dataImage.targetData[++i] = image.dataImage.sourceData[q];
image.dataImage.targetData[++i] = image.dataImage.sourceData[++q];
image.dataImage.targetData[++i] = image.dataImage.sourceData[++q];
image.dataImage.targetData[++i] = 255;
}
//context.clearRect(0, 0, width, height);
image.context.putImageData(image.dataImage.target, 0, 0);
if (image.landings){
image.circles.attr("r", 2)
.attr("cx", function(d){return projection([d.coord[0]-angle,d.coord[1]])[0];})
.attr("r", function(d){
var pos = (d.coord[0]-angle) % 360;
if ( ((pos > 90) && (pos < 270)) || ((pos < -90) && (pos > -270)) ){
return 0;
}
else {return landingR;}
});
// Update data of the groups (to position lines and boxes)
image.groups.each(function(d){
var pos = (d.coord[0]-angle) % 360;
// lateral
if ( (pos > 0 && pos <=90) || (pos < -270 && pos >= -360)) {
d3.select(this).data()[0].lateral = "right";
}
else {
d3.select(this).data()[0].lateral = "left";
}
// visibility
if ( ((pos > 90) && (pos < 270)) || ((pos < -90) && (pos > -270)) ){
d3.select(this).data()[0].visibility = "hidden";
}
else {
d3.select(this).data()[0].visibility = "visible";;
}
});
// Update position and visibility of lines
image.lines.attr("x2",function(d){
return projection([d.coord[0]-angle,d.coord[1]])[0];
})
.attr("x1",function(d){
if (d.lateral == "right"){ return W - padR;}
else { return padL;}
})
.attr("visibility",function(d){
return d.visibility;
});
// Update position and visibility of lines
image.labels.attr("x",function(d){
if (d.lateral == "right"){return W - padR;}
else {return padL;}
})
.attr("text-anchor",function(d){
if (d.lateral == "right"){return "start";}
else {return "end";}
})
.attr("visibility",function(d){
return d.visibility;
});
// Sort elements on the right
var right = image.groups.filter(function(d){
return d.lateral == "right" && d.visibility == "visible";
}).sort(function(a, b){
return d3.descending(a.coord[1], b.coord[1]);
});
right.select("line")
.attr("y1",function(d,i){return i*labelSpacing + paddingT;});
right.select("text")
.attr("y",function(d,i){return i*labelSpacing + paddingT;});
// Sort elements on the left
var left = image.groups.filter(function(d){
return d.lateral == "left" && d.visibility == "visible";
}).sort(function(a, b){
return d3.descending(a.coord[1], b.coord[1]);
});
left.select("line")
.attr("y1",function(d,i){return i*labelSpacing + paddingT;});
left.select("text")
.attr("y",function(d,i){return i*labelSpacing + paddingT;});
}
}
// Does the moving
d3.selectAll(".circdrag").call(drag);
var angle = 0;
var flag = 0;
var orig = [];
function dragmove(d) {
if (flag == 0){
orig = [d3.event.x,d3.event.y];
flag = 1;
}
if (!isNaN(projection.invert([d3.event.x,d3.event.y])[0])){
var dif = projection.invert(orig)[0] - projection.invert([d3.event.x,d3.event.y])[0];
}
else {return;}
var id = d3.select(this).attr("id");
id = id.substring(1, id.length);
angle = (images[id].angle + dif) % 360;
display(images[id], angle);
}
function dragend(d){
var id = d3.select(this).attr("id");
id = id.substring(1, id.length);
images[id].angle = angle;
flag = 0;
}
d3.selectAll(".circland").on("click", function(){
//var parent = d3.select(this.parentNode);
//parent.select("line").classed("high","true");
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment