Skip to contents

coord_3d() is the core of every ggcube plot. It controls how 3D data is rotated, projected, framed, and decorated. This article walks through each of its parameters in detail.

We’ll use a common base plot throughout:

p <- ggplot() +
  geom_function_3d(
    aes(fill = after_stat(z), color = after_stat(z)),
    fun = function(x, y) sin(x) * cos(y),
    xlim = c(-pi, pi), ylim = c(-pi, pi),
    n = 50, light = light("direct", contrast = .7)) +
  scale_fill_viridis_c() +
  scale_color_viridis_c() +
  theme_light() +
  theme(legend.position = "none",
        axis.text = element_blank())

Rotation

Three angles control how the plot is oriented: pitch, roll, and yaw rotate the plot around the x-, y-, and z-axes, respectively. All are specified in degrees. When they’re all zero, the plot resembles a standard 2D plot (+x = right, +y = up, +z = toward viewer). The defaults (pitch = 0, roll = -60, yaw = -30) give an overhead corner view that can work well for many plots.

Here are a variety of rotations showing the default, the zero-rotation case, the effect of setting each rotation angle individually while the others are held at zero, and an example combining them to achieve an arbitrary rotation (note this uses the patchwork library to produce a mutlti-panel plot):

(p + coord_3d() + ggtitle("Default")) +
  (p + coord_3d(pitch = 0, roll = 0, yaw = 0) + ggtitle("All angles 0")) +
  (p + coord_3d(pitch = 20, roll = 40, yaw = 60) + ggtitle("Arbitrary combination")) +
  (p + coord_3d(pitch = 30, roll = 0, yaw = 0) + ggtitle("pitch = 30")) +
  (p + coord_3d(pitch = 0, roll = 30, yaw = 0) + ggtitle("roll = 30")) +
  (p + coord_3d(pitch = 0, roll = 0, yaw = 30) + ggtitle("yaw = 30")) +
  plot_layout(nrow = 2)

Perspective and distance

By default, coord_3d() uses perspective projection, where objects farther from the viewer appear smaller. The dist parameter controls the camera distance from the center of the data cube — larger values produce less distortion. Set persp = FALSE to switch to orthographic projection, where parallel lines in 3D remain parallel in the 2D rendering. Orthographic projection is equivalent to viewing from infinite distance and can be useful for technical or schematic plots.

(p + coord_3d(yaw = 45, dist = 1.1) + ggtitle("dist = 1.1")) +
  (p + coord_3d(yaw = 45, dist = 2) + ggtitle("dist = 2")) +
  (p + coord_3d(yaw = 45, persp = FALSE) + ggtitle("persp = FALSE")) +
  plot_layout(ncol = 3)

Aspect ratios and scale matching

The scales parameter controls how data ranges map to visual cube dimensions. With "free" (the default), each axis is stretched independently to fill its dimension of the cube, maximizing the visual range for each variable. With "fixed", the visual proportions match the data proportions, analogous to coord_fixed() in 2D:

(p + coord_3d(scales = "free") + ggtitle('scales = "free"')) +
  (p + coord_3d(scales = "fixed") + ggtitle('scales = "fixed"')) +
  plot_layout(ncol = 2) & theme_light()

The ratio parameter sets custom relative lengths for the three axes. With "free" scales, ratios are applied to the standardized cube coordinates. With "fixed" scales, they’re applied to the data coordinates:

(p + coord_3d(scales = "free", ratio = c(1, 3, 1)) +
    ggtitle('free, ratio = c(1, 3, 1)')) +
  (p + coord_3d(scales = "fixed", ratio = c(1, 3, 1)) +
      ggtitle('fixed, ratio = c(1, 3, 1)')) +
  plot_layout(ncol = 2) & theme_light()

Panel selection

The panels argument controls which faces of the cube are drawn. The six faces are named "xmin", "xmax", "ymin", "ymax", "zmin", and "zmax". Depending on the rotation, some faces lie behind the data (“background” panels) and some lie in front (“foreground” panels). By default, only background panels are drawn. When drawn, foreground panels default to 20% opacity so they don’t obscure the data; their styling is controlled via theme elements (see the theming section below).

You can request specific faces by name, show all faces, or hide them entirely:

(p + coord_3d(panels = "background") + ggtitle('"background" (default)')) +
  (p + coord_3d(panels = c("xmin", "xmax", "zmax")) +
      ggtitle('c("xmin", "xmax", "zmax")')) +
  (p + coord_3d(panels = "all") + ggtitle('"all"')) +
  (p + coord_3d(panels = "none") + ggtitle('"none"')) +
  plot_layout(ncol = 2)

Zooming, clipping, and expansion

zoom adjusts the overall framing without changing the rotation or projection. Values less than 1 zoom out (more whitespace), while values greater than 1 zoom in (tighter framing).

The clip parameter controls whether graphical elements can be drawn outside the panel area (ggplot2’s rectangular canvas, not the projected 3D panel). The default "off" allows this, which is generally recommended for 3D plots where elements may extend beyond the projected panel area.

(p + coord_3d(zoom = 0.7) + ggtitle("zoom = 0.7")) +
  (p + coord_3d(zoom = 1) + ggtitle("zoom = 1\n(default)")) +
  (p + coord_3d(zoom = 1.5, clip = "on") + ggtitle('zoom = 1.5\nclip = "on"')) +
  plot_layout(ncol = 3)

The expand parameter works like the expand argument in standard ggplot2 scales — when TRUE (the default), axis ranges are padded slightly beyond the data range. You can fine-tune expansion per axis using the standard scale functions.

(p + coord_3d(expand = TRUE) + ggtitle("expand = TRUE (default)")) +
  (p + coord_3d(expand = FALSE) + ggtitle("expand = FALSE")) +
  plot_layout(ncol = 2)

Axis label placement

The xlabels, ylabels, and zlabels parameters control where axis tick labels and titles are placed. Each axis potentially has multiple possible edges where labels could appear (depending on which faces are visible). The default "auto" uses a heuristic that prioritizes edges on the periphery of the plot for readability.

For manual control, pass a length-2 character vector naming two adjacent faces — the shared edge between them is where the labels go. The first face determines the alignment plane:

(p + coord_3d() + ggtitle("auto (default)")) +
  (p + coord_3d(xlabels = c("ymax", "zmax"),
                zlabels = c("xmax", "ymin")) +
      ggtitle("manual placement")) +
  plot_layout(ncol = 2) & theme_light()

The rotate_labels parameter controls whether labels automatically rotate to align with the projected axis direction. Setting it to FALSE uses the angle specified in theme settings instead:

(p + coord_3d(rotate_labels = TRUE) + ggtitle("rotate_labels = TRUE")) +
  (p + coord_3d(rotate_labels = FALSE) + ggtitle("rotate_labels = FALSE")) +
  plot_layout(ncol = 2) & theme_light()

The title_position parameter affects axis titles that fall on internal (non-peripheral) edges. "auto" places them at the near end of the axis outside the plot area, while "center" centers them along the edge.

Theming the 3D cube

Standard ggplot2 themes work with ggcube. Adding a complete theme behaves as expected:

(p + coord_3d() + theme_dark() + ggtitle("theme_dark()")) +
  (p + coord_3d() + theme_minimal() + ggtitle("theme_minimal()")) +
  plot_layout(ncol = 2)

ggcube also adds several theme elements for 3D-specific styling. One important category is foreground panel elements, which control cube faces rendered in front of the data. These inherit from their background counterparts, so styling panel.background affects both background and foreground faces, while panel.foreground targets only the front faces. ggcube’s element_rect() extends ggplot2’s version with an alpha parameter for transparency. This is particularly useful for foreground panels — the default panel.foreground uses alpha = 0.2 so front faces don’t obscure the data:

p +
  coord_3d(panels = "all") +
  theme_gray() +
  theme(panel.border = element_rect(color = "black"),
        panel.foreground = element_rect(alpha = .3),
        panel.grid.foreground = element_line(color = "gray", linewidth = .25))

For axis labels, ggcube adds axis.text.z and axis.title.z elements that inherit from axis.text and axis.title respectively.

The margin argument to element_text() controls padding between titles, labels, and cube edges. Because ggcube’s current system for text spacing is fairly crude, margins need to be manually adjusted more often than in base ggplot2 figures.

p +
  coord_3d(panels = "all") +
  theme(panel.background = element_rect(color = "black"),
        panel.border = element_rect(color = "black"),
        panel.foreground = element_rect(alpha = .3),
        panel.grid.foreground = element_line(color = "gray", linewidth = .25),
        axis.text = element_text(color = "darkblue", size = 12),
        axis.text.z = element_text(color = "darkred"),
        axis.title = element_text(margin = margin(t = 30)),
        axis.title.x = element_text(color = "magenta"))

The full set of 3D-specific theme elements:

  • panel.foreground: Fill and border for foreground cube faces (inherits from panel.background)
  • panel.border.foreground: Border for foreground faces (inherits from panel.border)
  • panel.grid.foreground: Grid lines on foreground faces (inherits from panel.grid)
  • panel.grid.major.foreground: Major grid lines on foreground faces (inherits from panel.grid.foreground)
  • axis.text.z: Z-axis tick labels (inherits from axis.text)
  • axis.title.z: Z-axis title (inherits from axis.title)