Skip to content

Instantly share code, notes, and snippets.

Last active October 22, 2017 04:11
Show Gist options
  • Save vasturiano/2da88fb89cc75d18b20d8a7776fd6860 to your computer and use it in GitHub Desktop.
Save vasturiano/2da88fb89cc75d18b20d8a7776fd6860 to your computer and use it in GitHub Desktop.
Orbital Trajectory

A simulation of orbital trajectories using the d3-force simulation engine with the gravity-like d3-force-magnetic attraction force.

The initial tangential velocity of the blue orbiting body can be set relative to its orbital speed, calculated as √(GM/d). A factor of 1x (default) results in a perfectly circular orbit. Approaching the factor of √2x reaches the body's escape velocity, causing the body to drift away from the central object's gravity pull in an hyperbolic trajectory. Factors below 1 will cause a quicker fall of the blue body towards the attracting center, yielding elliptic orbits with increasing eccentricity.

The length of the pre-estimated trajectory can be manipulated by changing the number of samples (represented as dots).

See also Force-simulated Solar System and Hierarchical Orbits.

<script src="//"></script>
<script src="//"></script>
<link rel="stylesheet" href="style.css">
<canvas id="trails"></canvas>
<svg id="scaffold">
<circle r="10" style="fill: red"></circle>
<circle id="satellite" r="5" style="fill: rgba(0, 0, 255, .6)"></circle>
<circle id="ghost" r="5" style="fill: rgba(0, 0, 255, .6)"></circle>
<div id="controls">
<input class="slider-control" type="range" min="0.01" max="2" value="1" step="0.001" oninput="onVelocityChange(this.value)">
<span id="velocity-val">1</span>x orbital velocity
<input type="number" min="0" max="50000" value="5000" step="1000" oninput="onNumSamplesChange(this.value)" style="margin-top: 5px;">
<script src="index.js"></script>
const width = window.innerWidth, height = window.innerHeight;
const orbitDistance = height / 4,
G = 1e-3 * Math.pow(orbitDistance, 3), // Proportional to cube of orbit distance to maintain behavior over different heights
centralMass = 1,
orbitalV = Math.sqrt(G * centralMass / orbitDistance);
let initialV = orbitalV, numPnts = 5000;
// Draw scaffold canvas'#scaffold')
.attr('width', width)
.attr('height', height)
.attr('viewBox', `${-width/2} ${-height/2} ${width} ${height}`);'#ghost').attr('cy', -orbitDistance);
simulateTrajectory(orbitalV, numPnts);
function simulateTrajectory(initV, numTicks) {
const satellite = {
mass: 0,
x: 0,
y: -orbitDistance,
vx: initV,
vy: 0
forceSim = d3.forceSimulation()
.force('gravity', d3.forceMagnetic()
.charge(d => d.mass)
{ mass: centralMass },
// Clear canvas
const ctx ='canvas#trails')
.attr('width', width)
.attr('height', height)
ctx.translate(width/2, height/2);
ctx.fillStyle = 'rgba(0, 0, 75, .35)';
d3.range(numTicks).forEach(() => {
ctx.fillRect(satellite.x, satellite.y, 1, 1);
// Animate satellite
const elSatellite ='#satellite').datum(satellite);
satellite.x = 0;
satellite.y = -orbitDistance;
satellite.vx = initV;
satellite.vy = 0;
.on('tick', () => {
elSatellite.attr('cx', d => d.x)
.attr('cy', d => d.y);
// Event handlers
function onVelocityChange(relV) {'#velocity-val').text(relV);
initialV = relV * orbitalV;
simulateTrajectory(initialV, numPnts);
function onNumSamplesChange(num) {'#samples-val').text(num);
numPnts = num;
simulateTrajectory(initialV, numPnts);
body {
text-align: center;
font-family: Sans-serif;
margin: 0;
#scaffold, #controls {
position: absolute;
top: 0;
left: 0;
#controls {
font-size: 14px;
text-align: left;
margin: 8px;
padding: 1px 5px 5px 5px;
background: rgba(230, 230, 250, 0.7);
opacity: 0.5;
border-radius: 3px;
z-index: 1000;
#controls:hover {
opacity: 1;
.slider-control {
width: 350px;
position: relative;
top: 3px;
cursor: grab;
cursor: -webkit-grab;
.slider-control:active {
cursor: grabbing;
cursor: -webkit-grabbing;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment