|
<head> |
|
<title>Solar System</title> |
|
<meta charset "UTF-8"/> |
|
<link rel="stylesheet" type="text/css" href="solarSystemStyle.css" /> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
</head> |
|
<body> |
|
<h1> Planets of the Solar System </h1> |
|
<div id="OtherControls"> |
|
<div id="dateScale"></div> |
|
<div id="buttons"> |
|
<button id="startbtn"> Resume </button> |
|
<button id="fasterbtn">Faster</button> |
|
<button id="pausebtn">Pause</button> |
|
<button id="slowerbtn">Slower</button> |
|
</div> |
|
<div id="planetScale"> |
|
<!--Planet scale relative to Sun: |
|
<output name="planetScale">2500</output> |
|
<input type="range" min="100" max="5000" value="2500" step="100"></input>--> |
|
</div> |
|
</div> |
|
<div id="SolarSystem"></div> |
|
<div id="PolarAngle"></div> |
|
<div id="Description"> |
|
<!-- previous location of planetScale --> |
|
|
|
<table><tr><th colspan="2"> Planet details </table> |
|
|
|
<ul> |
|
<li> Sun diameter (white part) is the same scale as the orbits |
|
<li> Orbit shape and planet size: <a href="http://ssd.jpl.nasa.gov/?planet_phys_par">JPL</a> |
|
<li> Initial positions: <a href="http://ssd.jpl.nasa.gov/horizons.cgi?s_time=1#top">JPL</a> |
|
<li> Axial tilt direction: <a href="http://www.cso.caltech.edu/outreach/log/NIGHT_DAY/inclination.htm">Caltech's CSO</a> |
|
<li> Ring details: <a href="http://nssdc.gsfc.nasa.gov/planetary/factsheet/"> NASA fact sheets</a> |
|
<li> Inspiration: <a href="https://www.fourmilab.ch/cgi-bin/Solar/action">John Walker's site</a> |
|
<li> Enabling library: <a href="http://d3js.org">Mike Bostock's D3.js</a> |
|
<li> Equations: <a href="http://en.wikipedia.org/wiki/Kepler's_laws_of_planetary_motion"> |
|
Wikipedia</a> (or <a href="http://www.braeunig.us/space/orbmech.htm">deeper</a>) |
|
<li> Please link back! |
|
</ul> |
|
|
|
</div> |
|
<script> |
|
//<![CDATA[ |
|
var Cos=Math.cos, Sin=Math.sin, Pow=Math.pow; |
|
|
|
var above = true, |
|
asteroids = {"distance": 308171614, "width":181013424, "thickness": 42000000}, // wikipedia |
|
currentZoom = 1, |
|
dateFormatter = d3.time.format("%d %b %Y"), |
|
dateVal = new Date(), |
|
dt = 0.25, |
|
kmPerAu = 149597871, |
|
paused = false, |
|
planetScale = 2000, |
|
phi = 0; |
|
sun = {"name":"Sun", "r":"1390000", "p": "25.4"}, |
|
t0 = 2436115.811111111, // Start when Sputnik was lanched...10/4/1957 7:28 PM |
|
t = 2436115.811111111; |
|
|
|
/* Position on Julian Date: 2436115.811111111 |
|
N = Mean Motion (degrees per day) |
|
Tp = Perihelion Julian Date |
|
M = Mean Anomaly (degrees) |
|
TA = True Anomaly (degrees) |
|
*/ |
|
var positions = { |
|
"Mercury": {"N":4.092357023756561E+00, "Tp":2436107.650786827784, "M":3.339496039681397e+01, "TA":4.963264004656930e+01}, |
|
"Venus": {"N":1.602158559766484e+00, "Tp":2436009.280737817287, "M":1.706785494479548e+02, "TA":1.708036749106210e+02}, |
|
"Earth": {"N":9.863658540521377e-01, "Tp":2436208.075655013323, "M":2.695556802441351e+02, "TA":2.670444452891538e+02}, |
|
"Mars": {"N":5.239967637371190e-01, "Tp":2436394.139919182751, "M":2.141566053157155e+02, "TA":2.086886458959732e+02}, |
|
"Jupiter": {"N":8.310148115902946e-02, "Tp":2433970.537481698208, "M":1.782754160956198e+02, "TA":1.784341219838373e+02}, |
|
"Saturn": {"N":3.330865825573438e-02, "Tp":2431345.952658073977, "M":1.588775851404409e+02, "TA":1.609118518753897e+02}, |
|
"Uranus": {"N":1.167547963800946e-02, "Tp":2439354.989795456640, "M":3.221810352270507e+02, "TA":3.184800868678590e+02}, |
|
"Neptune": {"N":5.917334134481460e-03, "Tp":2414043.170068426523, "M":1.306111922800350E+02, "TA":1.308575374464771E+02}}; |
|
|
|
var definitions = { |
|
"a": "Semimajor axis (km)", |
|
"e": "Orbit Eccentrity", |
|
"eqi": "Axial tilt (deg)", |
|
"i": "Orbit Inclination to Ecliptic (deg)", |
|
"p": "Rotation Period (Earth days)", |
|
"r": "Mean Equatorial Radius (km)", |
|
"s": "Mean Orbit Velocity (km/h)" }; |
|
|
|
|
|
/*--------------------------------------------------------- Functions --- */ |
|
function getTA(planet, e) { |
|
// Return the angle (in radians) as a function of the current time (global t) |
|
// http://en.wikipedia.org/wiki/Kepler's_laws_of_planetary_motion |
|
// |
|
// M = Mean Motion = N t; t = 0 at perihelion (shortest distance) |
|
var M = positions[planet].N * (t - positions[planet].Tp); |
|
M = M < 0 ? 360 + (M % 360) : M % 360; |
|
M = M * Math.PI / 180; |
|
/* E = Eccentric Anomaly (nonlinear equation) |
|
M = E - epsilon sin(E) |
|
(Approximate with a series expansion about E = M) |
|
M = M - epsilon sin(M) |
|
+ (E - M) * (1 - epsilon cos(M)) |
|
+ 1/2 epsilon (E-M)^2 sin(M) + Order((E-M)^3) = O( epsilon^3 sin^3(M) ) |
|
## Subtract M; rearrange |
|
0 = (E-M)^2 + (E-M) * 2 * (1 - epsilon cos(M)) / (epsilon sin(M)) - 2 |
|
## If M is within an eighth of a degree of a multiple of 180, then just say E = M |
|
*/ |
|
E = M |
|
if (Math.abs(M % Math.PI) > .125) { |
|
var bb = (1 - e * Cos(M)) / (e * Sin(M)); |
|
E += (bb > 0) ? -bb + Math.sqrt(bb*bb + 2) : -bb - Math.sqrt(bb*bb + 2); |
|
} |
|
// TA = True Anomaly; tan^2 TA/2 = (1 + eps)/(1-eps) tan^2 E / 2 |
|
var TA = 2 * Math.atan(Math.sqrt( (1 + e) / (1 - e) * Pow(Math.tan(E / 2) , 2))); |
|
if ((E < 0) || (E > Math.PI)) { |
|
TA = 2* Math.PI - TA; |
|
} |
|
return TA; |
|
} |
|
|
|
function getRadius(TA, a, e) { |
|
// Return the radius for an ellipse with focus at 0,0 and perapsis at 0 degrees. |
|
return a / kmPerAu * (1 - e * e) / (1 + e * Cos(TA)); |
|
} |
|
|
|
function getHalfRingPath(X, Y, tilt, radius, heightscale, sweep, ringwidth, thickness) { |
|
// Return a path string for a half ellipse. |
|
// Parameters 'ringwidth' and 'thickness' are optional |
|
var r = radius, S = Sin(tilt), C = Cos(tilt); |
|
var dy = -r * S; |
|
var dx = -r * C; |
|
|
|
thickness = (typeof thickness == "undefined") ? 0 : thickness/2; |
|
thickness *= (sweep == 0) ? -1 : 1; |
|
|
|
path = [ "M", X - dx, Y - dy - thickness, // start pos |
|
"A", r, r * heightscale, // ellipse radii |
|
tilt * 180 / Math.PI, 1, sweep, // ellipse rotation, large arc, sweep direction |
|
X + dx, Y + dy - thickness]; // end pos |
|
|
|
if (typeof ringwidth == "undefined" || ringwidth == 0) |
|
return path.join(" "); |
|
|
|
sweep = (sweep == 0) ? 1 : 0; |
|
r += ringwidth; |
|
var dy = r * S; |
|
var dx = r * C; |
|
|
|
if (thickness != 0) { |
|
path = path.concat( |
|
"L", X - dx, Y - dy - thickness, // start pos |
|
"L", X - dx, Y - dy + thickness, |
|
"A", r, r * heightscale, // radii |
|
tilt*180/Math.PI, 1, sweep, |
|
X + dx, Y + dy + thickness, // end pos |
|
"L", X + dx, Y + dy - thickness, // start pos |
|
"z"); // close path |
|
} else { |
|
path = path.concat( |
|
"L", X - dx, Y - dy, // start pos |
|
"A", r, r * heightscale, // radii |
|
tilt*180/Math.PI, 1, sweep, |
|
X + dx, Y + dy, // start pos |
|
"z"); // close path |
|
} |
|
return path.join(" "); |
|
} |
|
|
|
|
|
function addGradientStops(selection, offsets, colors, opacities) { |
|
// Append stops with the stated offsets, colors, opacities to the selection. |
|
var c; |
|
for (i=0; i < offsets.length; i++) { |
|
c = typeof colors == 'string' ? colors : colors[i]; |
|
selection.append("stop") |
|
.attr("offset", offsets[i] + "%") |
|
.style({"stop-color":c, "stop-opacity":opacities[i]}); |
|
} |
|
} |
|
|
|
function addRings(selection, position) { |
|
// Add path container for rings to the selection; front or back. |
|
var rings = selection |
|
.append("g") |
|
.attr("name", function(d){ return "rings";}) |
|
.selectAll("path") |
|
.data(function(d) {return d.Rings;}) |
|
.enter().append("path") |
|
.attr("class", function(d) { |
|
var pattern = /Gap/g; |
|
return pattern.test(d.name) ? "rings gap " + position : "rings matter " + position;}) |
|
.attr("name", function(d) { return d.name;}) |
|
.property("position", position); |
|
|
|
// Have to bump the albedo or all rings but Saturn's are invisible here... |
|
rings.filter(".matter") |
|
.style("fill-opacity", function(d) { return 0.042 + d.albedo;}) |
|
rings.append("title").html(function(d) { return d.name;}); |
|
} |
|
|
|
|
|
function updateDateSlider(t) { |
|
var pos = (t - t0) / 100; |
|
d3.select("#dateScale .slider").attr({"x":pos - 1.5, "position": pos}); |
|
dateVal.setTime((t - 2440587.5) * 86400000 ); |
|
d3.select("#dateVal").attr("x", pos).text(dateFormatter(dateVal)); |
|
d3.select("#dt").text(Math.round(dt*100)/100 + " days / step"); |
|
} |
|
|
|
|
|
function updateTable(dat) { |
|
// Create or update the data display in the Description panel. |
|
var selected_entries = d3.entries(dat).filter( |
|
function(d) { |
|
if (d.key.length < 5) { |
|
return definitions.hasOwnProperty(d.key) ? true : false; |
|
} |
|
return (d.key == "Rings") ? false : true;}); |
|
|
|
if (d3.select("th").value != dat.name) { |
|
var table = d3.select("table").selectAll("tr") |
|
.data(selected_entries , function(d) { return (d == null) ? null : d.key;}); |
|
|
|
table.enter().append("tr").call(function(selection) { |
|
selection.append("td").attr("class", "key"); |
|
selection.append("td").attr("class", "value");}); |
|
|
|
table.exit().remove(); |
|
|
|
table.selectAll("td") |
|
.text(function(d){ |
|
if (this.classList.contains("key")) { return (d.key.length < 5) ? definitions[d.key] : d.key;} |
|
d.value = dat[d.key]; |
|
return d.value.toLocaleString();}); |
|
d3.select("table").insert("tr", "tr").append("th").text(dat.name).attr("colspan", 2); |
|
} |
|
} |
|
|
|
/*--------------------------------------------------------- Slider for date scale --- */ |
|
// UTC in seconds = ($julianDay - 2440587.5) * 86400; |
|
d3.select("#dateScale").append("svg") |
|
.attr({"viewBox":"-260 -12 710 30", "preserveAspectRatio":"xMidYMid meet"}) |
|
.call(function(tInput) { |
|
tInput.append("line") |
|
.attr({"x1":"-196.5", "x2":"385.33", "y1":"-9", "y2":"-9"}) |
|
.style({"stroke-width":3, "stroke":"royalblue"}); |
|
|
|
dateVal.setTime((t0 - 19650 - 2440587.5) * 86400000 ); |
|
tInput.append("svg:text").attr({"x":-199, "y":-7}) |
|
.style({"font-size":11, "alignment-baseline":"middle", "text-anchor":"end", "fill":"lightblue"}) |
|
.text(dateFormatter(dateVal)); |
|
|
|
dateVal.setTime((t0 + 38534 - 2440587.5) * 86400000 ); |
|
tInput.append("svg:text").attr({"x":389, "y":-7}) |
|
.style({"font-size":11, "alignment-baseline":"middle", "text-anchor":"start", "fill":"lightblue"}) |
|
.text(dateFormatter(dateVal)); |
|
tInput.append("svg:text").attr({"id":"dt", "x":448, "y":10}) |
|
.style({"font-size":11, "alignment-baseline":"middle","text-anchor":"end", "fill":"lightblue"}) |
|
.text(Math.round(dt*100)/100 + " days / step"); |
|
|
|
tInput.append("rect") |
|
.attr({"class":"slider", "position":0, "x":-2.5, "y":-10.5, "width":5, "height":8}) |
|
.style("stroke-width", "2px") |
|
.call( d3.behavior.drag() |
|
.origin(function() { return {"x":this.position, "y":0};}) |
|
.on("drag", |
|
function (d) { |
|
var pos = Math.round(d3.select(this).attr("position")); |
|
pos = Math.max(-196.5, Math.min(385.34, pos + d3.event.dx)); |
|
t = t0 + pos * 100; |
|
redraw(); |
|
})); |
|
|
|
dateVal.setTime((t - 2440587.5) * 86400000 ); |
|
tInput.append("svg:text").attr({"id":"dateVal", "x":0, "y":8}) |
|
.style({"font-size":11, "alignment-baseline":"middle", |
|
"text-anchor":"middle", "fill":"lightblue"}) |
|
.text(dateFormatter(dateVal)); |
|
}); |
|
|
|
/*--------------------------------------------------------- Slider for polar angle --- */ |
|
d3.select("#PolarAngle").append("svg") |
|
.attr({"viewBox":"-25 -20 35 220", "preserveAspectRatio":"xMinYMin meet"}) |
|
.call(function(phiInput) { |
|
phiInput.append("line") |
|
.attr({"x1":"3", "x2":"3", "y1":"-5", "y2":"185"}) |
|
.style({"stroke-width":1.5, "stroke":"royalblue"}); |
|
|
|
for (var y=0; y <= 180; y+=10) { |
|
phiInput |
|
.append("svg:text").attr({"x":-3, "y":y}) |
|
.style({"font-size":6, "alignment-baseline":"middle", "text-anchor":"end", "fill":"lightblue"}) |
|
.text( y + "\u00B0"); |
|
} |
|
phiInput |
|
.append("svg:text").attr({"x":-21, "y":-10}) |
|
.style({"font-size":6, "fill":"lightblue"}) |
|
.text("Polar Angle"); |
|
phiInput |
|
.append("polygon") |
|
.attr({"class":"slider", "points":"0,0 4,3.42 4,-3.42", "position":0}) |
|
.call( d3.behavior.drag() |
|
.origin(function() { return {"x":0, "y":this.position};}) |
|
.on("drag", |
|
function (d) { |
|
var pos = Math.round(d3.select(this).attr("position")); |
|
pos = Math.max(0, Math.min(180, pos + d3.event.dy)); |
|
var points = [0, pos, 4, pos + 3.42, 4, pos - 3.42]; |
|
d3.select(this).attr({"points": points.join(", "), "position": pos}) |
|
above = pos <= 90 ? true : false; |
|
phi = pos * Math.PI / 180; |
|
redraw();})); |
|
}); |
|
|
|
|
|
/*-------------------------------------------------------- Slider for planet scale --- */ |
|
d3.select("#planetScale").append("svg") |
|
.attr({"viewBox":"-16 -12 142 22", "preserveAspectRatio":"xMidYMid meet"}) |
|
.call(function(pInput) { |
|
pInput.append("line") |
|
.attr({"x1":0.2, "x2":100, "y1":-9, "y2":-9}) |
|
.style({"stroke-width":3, "stroke":"royalblue"}); |
|
pInput.append("svg:text").attr({"x":-1.5, "y":-7}) |
|
.style({"font-size":9, "alignment-baseline":"middle", "text-anchor":"end", "fill":"lightblue"}) |
|
.text("10:1"); |
|
pInput.append("svg:text").attr({"x":102, "y":-7}) |
|
.style({"font-size":9, "alignment-baseline":"middle", "text-anchor":"start", "fill":"lightblue"}) |
|
.text("5000:1"); |
|
pInput.append("rect") |
|
.attr({"class":"slider", "position":planetScale / 50, |
|
"x":planetScale / 50 - 2, "y":-12, "width":4, "height":7}) |
|
.style("stroke-width", "1px") |
|
.call( d3.behavior.drag() |
|
.origin(function() { return {"x":this.position, "y":0};}) |
|
.on("drag", |
|
function (d) { |
|
var pos = Math.round(d3.select(this).attr("position")); |
|
pos = Math.max(0.2, Math.min(100, pos + d3.event.dx)); |
|
planetScale = Math.round(pos * 5) * 10; |
|
d3.select(this).attr({"x":planetScale / 50 - 2, "position":planetScale / 50}); |
|
d3.select("#scaleVal") |
|
.text("Planet scale : sun = " + Math.round(planetScale) + ":1"); |
|
redraw(); |
|
})); |
|
|
|
pInput.append("svg:text").attr({"id":"scaleVal", "x":-14, "y":5}) |
|
.style({"font-size":9, "alignment-baseline":"middle", "text-anchor":"start", "fill":"lightblue"}) |
|
.text("Planet scale : sun = " + Math.round(planetScale) + ":1"); |
|
}); |
|
|
|
|
|
/*-------------------------------------------------------- Main (planet) section --- */ |
|
var system = d3.select("#SolarSystem").append("svg") |
|
.attr({"viewBox":"-32 -32 64 64", "preserveAspectRatio":"xMidYMid meet"}) |
|
.append("g") |
|
.call(d3.behavior.zoom().center([0, 0]).scaleExtent([0.5, 70]).on("zoom", zoom)) |
|
.append("g"); |
|
|
|
|
|
d3.json("orbitData.json", function (orbits) { |
|
orbits.forEach(function(p) { |
|
p._sin = { |
|
"eqi":Sin(Math.PI * p.eqi / 180), |
|
"i": Sin(Math.PI * p.i / 180) }; |
|
p._cos = { |
|
"eqi":Cos(Math.PI * p.eqi / 180), |
|
"i": Cos(Math.PI * p.i / 180) }; |
|
p._tan = { |
|
"eqi":Math.tan(Math.PI * p.eqi / 180)}; |
|
|
|
if (p.name == "Earth") { |
|
updateTable(p); |
|
} |
|
}); |
|
|
|
addGradientStops( |
|
system.append("defs").selectAll("radialGradient") |
|
.data(orbits) |
|
.enter().append("radialGradient") |
|
.attr("id", function(d) { return "lighting-" + d.name;}) |
|
.attr("name", function(d) { return d.name;}) |
|
.attr("r", "120%"), |
|
[0, 42, 100], "black", [0, 0.3, 1]); |
|
|
|
system.attr("id", "main") |
|
.append("rect") |
|
.attr({"x":"-32", "y":"-32", width:"64", height:"64"}) |
|
.style("fill", "url(#main-gradient)"); |
|
|
|
|
|
/* Gradient for shading the sky; centered around the sun. */ |
|
addGradientStops( |
|
system.select("defs") |
|
.insert("radialGradient") |
|
.attr("id", "main-gradient") |
|
.attr({"r":"42%", "fx":"50%", "fy":"50%", "cx":"50%", "cy":"50%"}), |
|
[0, 20, 50, 100], "blue", [0.5, 0.1, 0.05, 0]); |
|
|
|
/* Gradient for halo around the sun. */ |
|
addGradientStops( |
|
system.select("defs") |
|
.insert("radialGradient") |
|
.attr("id", "sun-halo") |
|
.attr({"r":"50%", "fx":"50%", "fy":"50%", "cx":"50%", "cy":"50%"}), |
|
[32, 34, 36, 45, 55, 100], |
|
["white", "white", "yellow", "yellow", "orange", "orange"], |
|
[1, 0.2, 0.8, 0.42, 0.46, 0]); |
|
|
|
/* Filter for clouds. */ |
|
system.select("defs") |
|
.insert("filter") |
|
.attr("id", "clouds").call(function(clouds) { |
|
clouds |
|
.append("feTurbulence") |
|
.attr( {"baseFrequency": "0.001,0.042", "numOctaves": 3, "stitchTiles": "stitch"}); |
|
clouds |
|
.append("feComponentTransfer").call(function(inner) { |
|
['R', 'G', 'B'].map(function(band) { |
|
inner.append("feFunc" + band).attr( {"type":"linear", "slope":12});}); |
|
}); |
|
clouds |
|
.append("feComposite") |
|
.attr({"operator":"arithmetic", "k2":0.2, "k3":0.5, "in":"SourceGraphic"}); |
|
clouds |
|
.append("feComposite") |
|
.attr({"operator":"in", "in2":"SourceGraphic"}); |
|
}); |
|
|
|
/* Planet content -- orbits and other data. */ |
|
var planets = system.selectAll("g") |
|
.data(orbits) |
|
.enter().append("g") |
|
.attr("class", "group") |
|
.attr("name", function(d) { return d.name;}) |
|
.on("mouseover", updateTable); |
|
|
|
planets.append("ellipse") |
|
.attr("class", "orbit") |
|
.attr("name", function(d) {return d.name;}) |
|
.attr({"rx":0, "ry":0}) |
|
.attr("cx", function(d) {return (d.e * d.a * d._cos.i) / kmPerAu;}) |
|
.attr("cy", 0) |
|
.style("stroke-width", 0.125); |
|
|
|
planets.filter(function(d){ return d.Rings != "No";}).call(addRings, "back") |
|
|
|
planets.append("circle") |
|
.attr({"class":"planet", "r":0}) |
|
.attr("id", function(d) { return d.name;}) |
|
.attr("name", function(d) { return d.name;}) |
|
.style({"stroke-width":0.142}); |
|
|
|
planets.append("path") |
|
.attr({"class":"equator"}) |
|
.attr("name", function(d) { return d.name;}) |
|
.style("stroke-width", 0.142); |
|
|
|
planets.filter(function(d){return d.r > 7000;}).append("circle") |
|
.attr({"class":"planet clouds", "r":0}) |
|
.attr("name", function(d) { return d.name;}) |
|
.style({"filter":"url(#clouds)"}); |
|
|
|
planets.append("circle") |
|
.attr({"class":"planet light", "r":0}) |
|
.attr("name", function(d) { return d.name;}) |
|
.style({"fill":function(d) { return "url(#lighting-" + d.name + ")";}}); |
|
|
|
planets.append("circle") |
|
.attr({"class":"daymark", "r":0.12 / kmPerAu}) |
|
.attr("name", function(d) { return d.name;}); |
|
|
|
planets.append("svg:text") |
|
.attr({"class":"planetName", "x":0, "y":0}) |
|
.attr("name", function(d) { return d.name;}) |
|
.style("font-size", function(d) { return 1.25;}) |
|
.text(function(d) { return d.name;}); |
|
|
|
planets.filter(function(d){ return d.Rings != "No";}).call(addRings, "front"); |
|
|
|
|
|
system.insert("g").attr({"class":"group", "name":"Sun"}) |
|
.call(function(sungroup) { |
|
sungroup |
|
.append("circle") |
|
.attr({"id":"Sun", "cx":"0", "cy":"0"}) |
|
.attr("r", 3 * sun.r / kmPerAu ) |
|
.style("fill", "url(#sun-halo)"); |
|
|
|
sungroup |
|
.append("svg:text") |
|
.attr({"class":"planetName", "x":sun.r / kmPerAu, "y": -sun.r / kmPerAu}) |
|
.attr("name", "Sun") |
|
.style("font-size", 1.25) |
|
.text("Sun"); |
|
}); |
|
|
|
system.insert("g") |
|
.attr({"class":"group", "name":"Asteroid Belt"}) |
|
.append("path").datum(asteroids) |
|
.attr({"class":"Asteroid-belt front"}) |
|
.property("position", "front") |
|
.style({"fill":"black", "fill-opacity":0.08, "stroke":"none"}) |
|
.append("title").html("Asteroid Belt"); |
|
|
|
system.insert("g", ".group[name=Jupiter]") |
|
.attr({"class":"group", "name":"Asteroid Belt"}) |
|
.append("path").datum(asteroids) |
|
.attr({"class":"Asteroid-belt back"}) |
|
.property("position", "back") |
|
.style({"fill":"black", "fill-opacity":0.08, "stroke":"none"}) |
|
.append("title").html("Asteroid Belt"); |
|
|
|
redraw(); |
|
} // END create |
|
) // END d3.json |
|
|
|
function zoom() { |
|
currentZoom = d3.event.scale; |
|
system.attr("transform", "translate(" + d3.event.translate + ")scale(" + currentZoom + ")"); |
|
system.selectAll(".orbit").style("stroke-width", 0.125 / currentZoom); |
|
system.selectAll(".planetName").style("font-size", 1.25 / currentZoom); |
|
system.selectAll(".planet").style("stroke-width", 0.142 / currentZoom); |
|
system.selectAll(".equator").style({"stroke-width":0.142 / currentZoom}); |
|
redraw(); |
|
} |
|
|
|
|
|
function redraw() { |
|
var _sin = {"phi": Sin(phi)}, _cos = {"phi": Cos(phi)}; |
|
|
|
system.selectAll(".group").filter(function(d){ return d != null;}).each(function(g) { |
|
var a, current, r, retrograde, R, tilt, TA, X, Y; |
|
|
|
current = d3.select(this); |
|
retrograde = g.eqi > 90; |
|
TA = getTA(g.name, g.e); |
|
_sin.TA = Sin(TA); |
|
_cos.TA = Cos(TA); |
|
_sin.t = Sin(t / g.p); |
|
_cos.t = Cos(t / g.p); |
|
tilt = Math.atan2(g._tan.eqi, -_sin.phi); |
|
|
|
r = g.r * planetScale / kmPerAu; |
|
R = getRadius(TA, g.a, g.e); |
|
/* Perihelion is left of the sun. X axis points left; Y axis points down. */ |
|
X = - R * g._cos.i * _cos.TA; |
|
Y = R * (_sin.TA * _cos.phi - g._sin.i * _cos.TA * _sin.phi); |
|
|
|
current.selectAll(".orbit") |
|
.attr("rx", function(d) { |
|
return d.a * Math.sqrt(Pow(d._cos.i,2) + Pow(d._sin.i,2)*Pow(_sin.phi,2) )/ kmPerAu;}) |
|
.attr("ry", function(d) { |
|
return Math.sqrt(Pow(d.a, 2) * (1 - Pow(d.e, 2)) * Pow(_cos.phi,2)) / kmPerAu;}) |
|
.attr("transform", function(d) { |
|
return "rotate(" + (Math.atan2(d._sin.i*_sin.phi, d._cos.i) * 180 / Math.PI) + |
|
"," + d.e * d.a * Math.sqrt( |
|
Pow(d._cos.i,2) + Pow(d._sin.i,2)*Pow(_sin.phi,2) )/ kmPerAu + |
|
",0)";}); |
|
|
|
current.selectAll(".planet") |
|
.attr({"r":r, "cx":X, "cy":Y}); |
|
|
|
current.selectAll(".daymark") |
|
.attr("r", function(d) { return (r < 0.12) ? r / 10 : 0.1 / currentZoom;}) |
|
.attr("cx", function(d) { |
|
return X + _cos.t * d._cos.eqi * r;}) |
|
.attr("cy", function(d) { |
|
return Y - (_sin.t * _cos.phi + d._sin.eqi * _sin.phi * _cos.t) * r;}) |
|
.style("fill", function(d) { |
|
var crossprod = |
|
_sin.phi * (- _sin.t * _cos.phi - d._sin.eqi * _sin.phi * _cos.t ) |
|
- d._tan.eqi * ( - _cos.t * d._cos.eqi ); |
|
|
|
var sweep = (retrograde == ! above) ? 1 : 0; |
|
return (crossprod > 0) == (sweep == 0) ? "none" : "darkgray";}); |
|
|
|
current.selectAll(".equator") |
|
.attr("d", |
|
function(d) { |
|
var ry, dx, dy, path, sweep; |
|
|
|
ry = r * _cos.phi * d._cos.eqi; |
|
sweep = (retrograde == above) ? 1 : 0; |
|
dy = -r * Sin(tilt); |
|
dx = -r * Cos(tilt); |
|
|
|
path = ["M", |
|
X-dx, Y-dy, // start pos |
|
"A", r , ry, // radii |
|
tilt*180/Math.PI, 1, sweep, |
|
// ellipse rotation, large arc, sweep direction |
|
X+dx, Y+dy]; // end pos |
|
|
|
return path.join(" "); |
|
}); |
|
|
|
|
|
current.selectAll(".rings") |
|
.attr("d", |
|
function(d) { |
|
var sweep; |
|
|
|
if (this.classList.contains("front")) { sweep = (retrograde == above) ? 1 : 0; |
|
} else { sweep = (retrograde == above) ? 0 : 1; } |
|
|
|
return getHalfRingPath(X, Y, tilt, |
|
d.distance * planetScale / kmPerAu, |
|
_cos.phi * g._cos.eqi, |
|
sweep, |
|
d.width * planetScale / kmPerAu); |
|
}); |
|
|
|
current.selectAll(".planetName") |
|
.attr("x", function(d) { return X + planetScale * 0.8 * d.r / kmPerAu;}) |
|
.attr("y", function(d) { return Y + planetScale * 1.1 * d.r / kmPerAu;}); |
|
|
|
d3.select("#lighting-" + g.name) |
|
.attr("fx", function(d) { return (50 + 45 * _cos.TA ) + "%";}) |
|
.attr("fy", function(d) { return (50 - 45 * _sin.TA * _cos.phi) + "%";}) |
|
.attr("cx", function(d) { return (50 + 25 * _cos.TA ) + "%";}) |
|
.attr("cy", function(d) { return (50 - 25 * _sin.TA * _cos.phi) + "%";}); |
|
}); |
|
|
|
d3.selectAll(".Asteroid-belt") |
|
.attr("d", |
|
function(d) { |
|
return getHalfRingPath(0, 0, 0, |
|
d.distance / kmPerAu, |
|
_cos.phi, |
|
(this.classList.contains("front") == above) ? 1 : 0, |
|
(d.width > 100 ? d.width : 100) / kmPerAu, |
|
d.thickness * Sin(phi) / kmPerAu);}); |
|
|
|
updateDateSlider(t); |
|
} |
|
|
|
d3.select("#startbtn").on("click", function() { paused = false;}); |
|
d3.select("#pausebtn").on("click", function() { paused = true;}); |
|
d3.select("#fasterbtn").on("click", function() { dt *= 1.1; redraw();}); |
|
d3.select("#slowerbtn").on("click", function() { dt *= 0.9; redraw();}); |
|
|
|
d3.timer(function() { |
|
if (! paused && t < 2516022.5) { |
|
t += dt; |
|
redraw(); |
|
} |
|
}); |
|
|
|
//]]> |
|
</script> |
|
</body> |
|
</html> |