Skip to content

Instantly share code, notes, and snippets.

@tlfrd
Last active August 31, 2017 23:22
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 tlfrd/155feca25ebe85b6f31b56945e912b4e to your computer and use it in GitHub Desktop.
Save tlfrd/155feca25ebe85b6f31b56945e912b4e to your computer and use it in GitHub Desktop.
Solar System
license: mit

A pseudo-3d solar system in d3 using orthographic projections and radial gradients for shading. Each planet rotates at its relative velocity to Earth. Hover over each planet to reveal info.

Future ideas: add a glowing Sun and orbiting moons.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin: 0; position: fixed; top: 0; right: 0; bottom: 0; left: 0; }
svg {
background-color: black;
}
.bounding-box {
fill: white;
fill-opacity: 0;
stroke: #fff;
stroke-opacity: 0.5;
stroke-dasharray: 3,3;
}
.label {
font-family: monospace;
opacity: 1;
font-size: 10px;
fill: #fff;
}
.info {
font-family: monospace;
opacity: 1;
font-size: 8px;
fill: #fff;
}
.planet circle, .graticule {
fill: none;
stroke: #123;
stroke-opacity: 0.15;
}
.planet circle {
stroke-width: 1px;
}
.axis-line {
stroke: #fff;
stroke-opacity: 0.3;
}
.star {
fill: white;
opacity: 1;
}
</style>
</head>
<body>
<script>
var margin = {top: 100, right: 50, bottom: 100, left: 50};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var starArea = d3.select("svg").append("g");
var config = {
padding: 10,
axisMultiplier: 1.4,
velocity: [0.01, 0],
starRadius: 1,
glowRadius: 2
};
var solar = [
{name: "Mercury", tilt: 0.03, radius: 2439.7, period: 58.65, colours: ["#e7e8ec", "#b1adad"]},
{name: "Venus", tilt: 2.64, radius: 6051.8, period: -243, colours: ["#f8e2b0", "#d3a567"]},
{name: "Earth", tilt: 23.44, radius: 6371, period: 1, colours: ["#9fc164", "#6b93d6"]},
{name: "Mars", tilt: 6.68, radius: 3389.5, period: 1.03, colours: ["#ef1501", "#ad0000"]},
{name: "Jupiter", tilt: 25.19, radius: 69911, period: 0.41, colours: ["#d8ca9d", " #a59186"]},
{name: "Saturn", tilt: 26.73, radius: 58232, period: 0.44, colours: ["#f4d587", "#f4a587"]},
{name: "Uranus", tilt: 82.23, radius: 25362, period: -0.72, colours: ["#e1eeee", "#adb0c3"]},
{name: "Neptune", tilt: 28.32, radius: 24622, period: 0.72, colours: ["#85addb", " #3f54ba"]}
];
var definitions = d3.select("svg").append("defs");
var filter = definitions.append("filter")
.attr("id", "glow");
filter.append("feGaussianBlur")
.attr("class", "blur")
.attr("stdDeviation", config.glowRadius)
.attr("result","coloredBlur");
var feMerge = filter.append("feMerge")
feMerge.append("feMergeNode")
.attr("in","coloredBlur");
feMerge.append("feMergeNode")
.attr("in","SourceGraphic");
function generateStars(number) {
var stars = starArea.selectAll("circle")
.data(d3.range(number).map(d =>
i = {x: Math.random() * (width + margin.left + margin.right), y: Math.random() * (height + margin.top + margin.bottom), r: Math.random() * config.starRadius}
))
.enter().append("circle")
.attr("class", "star")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.r);
}
function displayPlanets(cfg, planets) {
var boundingSize = (width / planets.length) - cfg.padding;
var boundingArea = svg.append("g")
.selectAll("g")
.data(planets)
.enter().append("g")
.attr("transform", (d, i) => "translate(" + [i * (boundingSize + cfg.padding), height / 2] + ")")
.on("mouseover", showInfo)
.on("mouseout", hideInfo);
var boundingRect = boundingArea.append("rect")
.attr("class", "bounding-box")
.attr("y", -boundingSize / 2)
.attr("width", boundingSize)
.attr("height", boundingSize);
var info = boundingArea.append("g")
.attr("transform", "translate(" + [0, (boundingSize / 2) + 18] + ")")
.attr("class", "info")
.style("opacity", 0);
info.append("text")
.text(d => "Radius: " + d.radius + "km");
info.append("text")
.attr("y", 12)
.text(d => "Tilt: " + d.tilt + "°");
info.append("text")
.attr("y", 24)
.text(d => "Day Length: " + d.period);
var labels = boundingArea.append("text")
.attr("class", "label")
.attr("y", -boundingSize / 2)
.attr("dy", -12)
.text(d => d.name);
var radiusScale = d3.scaleLinear()
.domain([0, d3.max(planets, d => d.radius)])
.range([0, (boundingSize / 2) - 3]);
var graticuleScale = d3.scaleLinear()
.domain(d3.extent(planets, d => d.radius))
.range([20, 10]);
var planets = boundingArea.each(function(d) {
var x = d3.select(this);
drawPlanet(x, d);
});
function drawPlanet(element, data) {
var rotation = [0, 0, data.tilt];
var projection = d3.geoOrthographic()
.translate([0, 0])
.scale(radiusScale(data.radius))
.clipAngle(90)
.precision(0.1);
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule();
var planet = element.append("g")
.attr("class", "planet")
.attr("transform", "translate(" + [boundingSize / 2, 0] + ")");
var defs = d3.select("svg").select("defs");
var gradient = defs.append("radialGradient")
.attr("id", "gradient" + data.name)
.attr("cx", "25%")
.attr("cy", "25%");
// The offset at which the gradient starts
gradient.append("stop")
.attr("offset", "5%")
.attr("stop-color", data.colours[0]);
// The offset at which the gradient ends
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", data.colours[1]);
var axis = planet.append("line")
.attr("class", "axis-line")
.attr("x1", -radiusScale(data.radius) * cfg.axisMultiplier)
.attr("x2", radiusScale(data.radius) * cfg.axisMultiplier)
.attr("transform", "rotate(" + (90 - data.tilt) + ")");
var fill = planet.append("circle")
.attr("r", radiusScale(data.radius))
.style("fill", "url(#gradient" + data.name + ")")
.style("filter", "url(#glow)");
var gridLines = planet.append("path")
.attr("class", "graticule")
.datum(graticule.step([graticuleScale(data.radius), graticuleScale(data.radius)]))
.attr("d", path);
d3.timer(function(elapsed) {
// Rotate projection
projection.rotate([rotation[0] + elapsed * cfg.velocity[0] / data.period, rotation[1] + elapsed * cfg.velocity[1] / data.period, rotation[2]]);
// Redraw gridlines
gridLines.attr("d", path);
})
}
}
function showInfo(d) {
d3.select(this).select("g.info")
.transition()
.style("opacity", 1);
}
function hideInfo(d) {
d3.select(this).select("g.info")
.transition()
.style("opacity", 0);
}
generateStars(500);
displayPlanets(config, solar);
starArea.lower();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment