Quick Start Guide

Installation

# Install from CRAN:
install.packages("orbitr")

# Or install the development version from GitHub:
# install.packages("devtools")
devtools::install_github("DRosenman/orbitr")

For 3D interactive plotting, you’ll also want:

install.packages("plotly")

Your First Simulation in 30 Seconds

orbitr is designed around a simple pipe-friendly workflow: create a system, add bodies, simulate, and plot.

library(orbitr)

create_system() |>
  add_body("Earth", mass = mass_earth) |>
  add_body("Moon",  mass = mass_moon, x = distance_earth_moon, vy = speed_moon) |>
  simulate_system(time_step = seconds_per_hour, duration = seconds_per_day * 28) |>
  plot_orbits()

That’s it — a 28-day lunar orbit in four lines. Here’s what each step does:

  1. create_system() initializes an empty simulation with standard gravitational constant G.
  2. add_body() places a body with a given mass, position, and velocity. All positions are in meters, velocities in m/s. The built-in constants (mass_earth, distance_earth_moon, etc.) save you from looking anything up.
  3. simulate_system() runs the N-body integration forward in time. time_step is how many seconds per integration step, duration is the total time to simulate.
  4. plot_orbits() produces a quick 2D trajectory plot using ggplot2.

Customizing the Plot

By default, plot_orbits() returns a standard ggplot object for planar (2D) simulations and a plotly HTML widget for simulations with any 3D motion. (You can also force 3D rendering on planar data with three_d = TRUE.) Because the 2D case returns a regular ggplot, you can layer additional geoms, scales, themes, and labels onto it with + like any other ggplot.

A common annoyance is that the central body in a two-body system can be invisible: plot_orbits() draws each body as a geom_path() of its trajectory, and a much more massive body barely moves so its path is too small to see. The Sun in a Sun-Earth simulation is the classic example — it’s there, but its loop around the barycenter is well inside the Sun itself. The simplest fix is to drop a marker at the origin:

sim <- create_system() |>
  add_sun() |>
  add_body("Earth", mass = mass_earth, x = distance_earth_sun, vy = speed_earth) |>
  simulate_system(time_step = seconds_per_day, duration = seconds_per_year)

sim |>
  plot_orbits() +
  ggplot2::geom_point(
    data = data.frame(x = 0, y = 0),
    ggplot2::aes(x = x, y = y),
    color = "#00BFC4",
    size = 6
  ) +
  ggplot2::labs(title = "Earth-Sun Orbit")

This works because the Sun sits essentially at the origin throughout the simulation. For systems where the central body actually moves a noticeable amount, you’d want to pull its position from the simulation tibble instead of hardcoding (0, 0).

Watching the Orbit in Motion

Static plots are nice, but you can also play the simulation forward as an animation with animate_system(). By default each body leaves a fading wake of recent positions behind it:

animate_system(sim, fps = 15, duration = 5)

animate_system() is the animated counterpart to plot_system() — it samples the simulation tibble down to roughly fps * duration evenly spaced frames, then renders them as a GIF using gganimate. Like the static plotters, it auto-dispatches to a 3D version (animate_system_3d(), an interactive plotly widget with a play button) the moment any body has non-zero Z motion. The 2D path requires the gganimate and gifski packages — install them with install.packages(c("gganimate", "gifski")).

Adding More Bodies

Since orbitr is a full N-body engine, you can add as many bodies as you want. Each one gravitationally interacts with every other.

For real solar system bodies, there are two ways to add them:

The manual way — use add_body() with explicit positions and velocities. This gives you full control and works for any body, real or fictional:

create_system() |>
  add_sun() |>
  add_body("Earth", mass = mass_earth, x = distance_earth_sun, vy = speed_earth) |>
  add_body("Venus", mass = mass_venus, x = distance_venus_sun, vy = speed_venus) |>
  simulate_system(time_step = seconds_per_day, duration = seconds_per_year) |>
  plot_orbits()

Venus completes more than one full orbit in the same time Earth takes to go around once — you can see the inner orbit is both smaller and faster.

The easy way — use add_sun() and add_planet(), which use real masses and orbital data from JPL automatically. Any orbital element can be overridden for “what if” scenarios:

create_system() |>
  add_sun() |>
  add_planet("Earth", parent = "Sun") |>
  add_planet("Mars",  parent = "Sun") |>
  simulate_system(time_step = seconds_per_day, duration = seconds_per_year * 2) |>
  plot_orbits(three_d = FALSE)

And load_solar_system() gives you the whole thing in one call:

load_solar_system() |>
  simulate_system(time_step = seconds_per_day, duration = seconds_per_year) |>
  plot_orbits(three_d = FALSE)

Under the hood, these use Keplerian orbital elements — a way to describe orbits by their shape and orientation instead of raw positions and velocities. See Keplerian Orbital Elements for the full explanation.

Removing Bodies

You can drop bodies from a system with remove_body(). This is handy when you want to start from load_solar_system() but don’t need every body:

load_solar_system() |>
  remove_body(c("Pluto", "Moon")) |>
  simulate_system(time_step = seconds_per_day, duration = seconds_per_year) |>
  plot_orbits(three_d = FALSE)

remove_body() accepts a single name or a character vector and works anywhere in the pipe chain — before or after adding bodies, but always before simulating.

The Output is Just a Tibble

simulate_system() returns a standard tidy tibble. You can use dplyr, ggplot2, plotly, or any other tool on it:

sim <- create_system() |>
  add_body("Earth", mass = mass_earth) |>
  add_body("Moon",  mass = mass_moon, x = distance_earth_moon, vy = speed_moon) |>
  simulate_system(time_step = seconds_per_hour, duration = seconds_per_day * 28)

sim
#> # A tibble: 1,346 × 9
#>    id       mass          x           y     z      vx          vy    vz  time
#>    <chr>   <dbl>      <dbl>       <dbl> <dbl>   <dbl>       <dbl> <dbl> <dbl>
#>  1 Earth 5.97e24         0         0        0   0        0            0     0
#>  2 Moon  7.34e22 384400000         0        0   0     1022            0     0
#>  3 Earth 5.97e24       215.        0        0   0.119    0.000571     0  3600
#>  4 Moon  7.34e22 384382520.  3679200        0  -9.71  1022.           0  3600
#>  5 Earth 5.97e24       860.        4.11     0   0.239    0.00229      0  7200
#>  6 Moon  7.34e22 384330083.  7358065.       0 -19.4   1022.           0  7200
#>  7 Earth 5.97e24      1934.       16.5      0   0.358    0.00514      0 10800
#>  8 Moon  7.34e22 384242692. 11036262.       0 -29.1   1022.           0 10800
#>  9 Earth 5.97e24      3438.       41.1      0   0.477    0.00914      0 14400
#> 10 Moon  7.34e22 384120357. 14713454.       0 -38.8   1021.           0 14400
#> # ℹ 1,336 more rows

Each row is one body at one point in time, with columns for position (x, y, z), velocity (vx, vy, vz), mass, body ID, and time.

Saving and Sharing Systems

You can save a full system to disk and restore it later:

save_system(sys, "my_system.rds")
restored <- load_system("my_system.rds")

To share body data as a CSV — for collaborators, Python, or Excel — use export_bodies():

export_bodies(sys, "bodies.csv")

save_system() / load_system() preserves everything (bodies, gravitational constant, class) as an R object. export_bodies() writes a plain CSV with just the body table, which anyone can open.

Next Steps