Skip to contents

Creates an interactive HTML widget that you can rotate by dragging. Pre-renders a set of ggcube plots across a 1D or 2D grid of rotation angles and packages them as an htmlwidgets widget. The widget displays in the RStudio Viewer, embeds in R Markdown and Quarto documents, works in Shiny (see orbit3dOutput()), and can be written to a self-contained HTML file with file =. When viewing it, navigate through the views by dragging the image or using the keyboard arrows.

Usage

orbit_3d(
  plot,
  pitch = NULL,
  roll = NULL,
  yaw = NULL,
  light_azimuth = NULL,
  light_elevation = NULL,
  n = 36,
  loop = NULL,
  start = NULL,
  max_frames = 500,
  width = 480,
  height = 480,
  res = 96,
  cores = 1,
  device = "png",
  progress = interactive(),
  file = NULL,
  readout = FALSE
)

Arguments

plot

A ggplot object that uses coord_3d().

pitch, roll, yaw

Rotation angles in degrees. Each is either a single value (held fixed) or a length-2 range c(from, to) that becomes a draggable axis. For yaw, using a larger value for from than to gives more natural results. Values specified here replace the ones specified in coord_3d(). See Details.

light_azimuth, light_elevation

Light direction in degrees, using the same conventions as direction(): azimuth is a bearing clockwise from the positive y axis, and elevation is the angle above the xy plane (between -90 and 90). Each is either a single value (held fixed) or a length-2 range c(from, to) that becomes a draggable axis. When either is given, the other is held at the plot light's current value unless also given; when both are NULL (the default), lighting is left as the plot specifies. These move the plot-level light set with light() or coord_3d(light = ), which must be directional (not position-based); it is an error to use them when lighting is disabled or when any layer has its own light. The light's anchor is respected: with the default anchor = "scene" the light moves around the data, so orbiting yaw and light_azimuth together partly cancels out; with anchor = "camera" it moves around the viewer.

n

Grid resolution. For a 1D orbit, a single number giving the frame count. For a 2D orbit, a length-2 vector giving the counts for the horizontal and vertical screen axes respectively (in that order, x then y). Its length must match the number of ranged parameters. Default is 36.

loop

Controls seamless wraparound. NULL (default) auto-detects looping independently for each axis by checking whether that axis returns to its starting view. TRUE or FALSE force wrapping on or off for all axes.

start

Initial viewing angle(s) the orbit opens at, given in degrees as a named vector matching the variable axes, e.g. start = c(yaw = 30), start = c(yaw = 30, roll = 45), or start = c(light_azimuth = 90). For a 1D orbit a bare scalar (e.g. start = 30) is also accepted. The orbit opens at the frame nearest each given angle. Any variable axis not named in start opens at the midpoint of its range, as does every axis when start = NULL (the default). Naming an axis that is fixed rather than variable throws an error.

max_frames

Safety cap on the total number of rendered frames (prod(n)). A 2D grid can become very large; if the requested grid exceeds this cap the function errors rather than rendering. Raise it to allow larger grids. Default is 500.

width, height

Dimensions of the animation in pixels. Defaults to 480 x 480.

res

Resolution in ppi for the rendered frames. Default is 96. Controls the size of point-based text/lines/point elements relative to the plot. See ?grDevices::png for details.

cores

Number of CPU cores for parallel frame rendering. Default is 1 (sequential). Values greater than 1 use parallel::parLapply() to render frames in parallel, which can significantly speed up animation of complex plots. Note that progress reporting is not available during parallel rendering.

device

The graphics device to use for rendering frames. Default is "png". Other options include "ragg_png" (requires the ragg package).

progress

Whether to print a progress bar. Ignored when cores > 1.

file

Optional path to write a self-contained HTML file (via htmlwidgets::saveWidget()). If NULL (default), no file is written and the widget is simply returned for display or embedding.

readout

Logical. Whether the orbit opens with a readout in its corner showing the angles of the current view, which you can copy into coord_3d() to reproduce it. The readout always lists pitch, roll, and yaw, and adds the light's azimuth and elevation (for use with direction()) when the orbit moves the light. Angles are shown in the terms you gave them: an orbit over yaw = c(0, 360) reports values from 0 to 360. This sets only how the orbit opens: pressing r while the orbit has focus shows or hides the readout either way. Default is FALSE.

Value

A orbit_3d htmlwidget. Printed at the console it opens in the RStudio Viewer or browser; in a knitr document it embeds automatically; in Shiny use orbit3dOutput() and renderOrbit3d().

Specifying rotation and lighting

Each of pitch, roll, yaw, light_azimuth, and light_elevation is given either as a single value (held fixed) or as a length-2 range c(from, to) that is a variable, draggable axis. The number of ranges determines the interaction:

  • One range gives a 1D orbit: horizontal drag (or left/right keyboard arrows) vary that one parameter across its range.

  • Two ranges give a 2D orbit: horizontal drag (or left/right keyboard arrows) vary one parameter while vertical drag/arrows vary the other.

At least one range is required. Supplying three ranges is an error.

For a 2D orbit, the horizontal (x) and vertical (y) axes are assigned from whichever parameters are ranged, by priority. The x axis takes the highest-priority ranged parameter in the order yaw, pitch, roll, light_azimuth, light_elevation, and the y axis takes the other. Camera parameters outrank light parameters, so an orbit that ranges one of each puts the camera on horizontal drag and the light on vertical drag. In the common case yaw = c(0, 360), roll = c(...) this puts yaw on horizontal drag and roll on vertical drag; yaw = c(0, 360), light_elevation = c(...) spins the camera horizontally and raises the light vertically; and light_azimuth = c(0, 360), light_elevation = c(...) moves only the light, with azimuth horizontal and elevation vertical.

The range direction follows the order the values are written: for example yaw = c(360, 0) rotates the opposite way from yaw = c(0, 360).

Looping

When a variable axis returns to its starting view (for example a full yaw = c(0, 360) turn), that axis loops seamlessly: dragging past either end wraps around. This is detected per axis by comparing the first and last angle modulo 360, so a full-turn axis wraps while a partial range (such as roll = c(-90, 90)) clamps. loop can force this on or off for all axes at once.

Keyboard

Once the widget has focus, the arrow keys step one frame: left/right move the horizontal axis, and (in a 2D orbit) up/down move the vertical axis. The r key shows or hides the angle readout (see readout).

See also

animate_3d() for GIF/MP4 output. facet_wrap_views() and facet_grid_views() for a static figure showing several views side by side. orbit_3d-shiny for including orbits in Shiny apps.

Examples

# \donttest{
p <- ggplot(mountain, aes(x, y, z, fill = z, color = z)) +
  geom_surface_3d(linewidth = .25) +
  coord_3d(light = light(anchor = "camera",direction = c(1, 1, 0)),
           ratio = c(1.5, 2, 1)) +
  scale_fill_viridis_c() + scale_color_viridis_c() +
  theme_void()

# 1D drag-to-rotate turntable (small n to minimize runtime)
orbit_3d(p, yaw = c(360, 0), n = 12)
#> Rendering 12 frames...
#> Assembling orbit (press `r` on the orbit to toggle its angle readout)...
# 2D orbit: drag horizontally to spin (yaw), vertically to tilt (roll) # (note you could increase `cores` to speed up compute time) orbit_3d(p, yaw = c(360, 0), roll = c(-90, 0), n = c(20, 10), width = 1000, height = 1000, cores = 1) #> Rendering 200 frames... #> Assembling orbit (press `r` on the orbit to toggle its angle readout)...
# Light orbit: drag horizontally to move the light around, # vertically to raise and lower it orbit_3d(p, light_azimuth = c(0, 360), light_elevation = c(0, 90), n = c(12, 4)) #> Rendering 48 frames... #> Assembling orbit (press `r` on the orbit to toggle its angle readout)...
# Save to a chosen path orbit_3d(p, yaw = c(360, 0), n = 12, file = file.path(tempdir(), "surface.html")) #> Rendering 12 frames... #> Assembling orbit (press `r` on the orbit to toggle its angle readout)...
# }