D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
Mavromatika
Full window
Github gist
Exploring the Solar System
<!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="https://nssdc.gsfc.nasa.gov/planetary">landing coordinates</a>, and for the maps of <a href="https://maps.jpl.nasa.gov/venus.html">Venus</a>, <a href="https://www.mars.asu.edu/data/mdim_color/">Mars</a>, and the <a href="https://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>
https://d3js.org/d3.v3.min.js
https://d3js.org/d3.geo.projection.v0.min.js