---
title: "Introduction to ggcube"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Introduction to ggcube}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---
  
```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5,
  dpi = 150,
  warning = FALSE,
  message = FALSE
)
```

ggcube is a ggplot2 extension for 3D plotting. It provides 3D geoms, stats, and a
3D coordinate system that let you build 3D visualizations using the familiar ggplot2
grammar: map your data to aesthetics, add layers, and customize the result with scales,
guides, and themes.

```{r setup, message = FALSE}
library(ggcube)
```


## The basics

The essential ingredient of a ggcube plot is `coord_3d()`. Adding it to a standard
ggplot that includes a `z` aesthetic creates a 3D plot:

```{r basics}
ggplot(mpg, aes(x = displ, y = hwy, z = drv, color = class)) +
      geom_point() +
      coord_3d()
```

Some standard ggplot2 layers like `geom_point()` work in 3D automatically. ggcube also
provides 3D-specific layer functions for surfaces, paths, volumes, and text, described
below.

Because ggcube works within ggplot2's 2D graphics engine, there are some important things
to understand about how rendering works. Each 3D layer projects its geometry onto a 2D
plane, with depth sorting to determine which elements appear in front of others. This
sorting happens *within* each layer, but not *across* layers — just as in standard
ggplot2, later layers are drawn on top of earlier ones regardless of their 3D depth.
This means that layer order in your code matters, and complex multi-layer scenes may
require some thought about stacking.


## Controlling the view

`coord_3d()` controls how the 3D scene is projected onto 2D. Rotation is specified via
three angles --- `pitch` (tilt around the x-axis), `roll` (tilt around the y-axis), and
`yaw` (spin around the z-axis):

```{r rotation, fig.height = 4}
ggplot(mpg, aes(displ, hwy, drv, color = class)) +
      geom_point() +
      coord_3d(pitch = 0, roll = 60, yaw = 0, dist = 1.4,
               ratio = c(2, 1, 1), panels = "all") +
      theme(panel.border = element_rect(color = "black"),
            panel.foreground = element_rect(alpha = .1))
```

Perspective projection is on by default, making distant objects appear smaller. The
`dist` parameter controls the strength of this effect (larger values = less distortion),
and `persp = FALSE` switches to orthographic projection where parallel lines stay
parallel.

The `scales` parameter controls how axis ranges map to visual size. `"free"` (the
default) stretches each axis independently to fill the cube, while `"fixed"` preserves
the relative scale of the data (like `coord_fixed()` in 2D). The `ratio` parameter lets
you set custom proportions for the three axes. And `zoom` adjusts overall framing without
changing the rotation or projection.

See the [3D view](https://matthewkling.github.io/ggcube/articles/coord_3d.html) article for a comprehensive guide to 
all view parameters.


## 3D layers

Most standard ggplot2 geoms are designed around 2D coordinate assumptions, and won't
render correctly with `coord_3d()`. (An exception is `geom_point()`, which works out
of the box as shown above, albeit with ordering and sizing limitations.) ggcube provides 
a range of 3D-native layer functions that cover common 3D plot types, including points, 
surfaces, bars, paths, and text.

Here's a quick tour of the main categories.

### Surfaces

Several geoms and stats work together to render surfaces. `geom_surface_3d()` renders
data as a tessellated surface, `geom_contour_3d()` creates layer-cake
contour stacks, and `geom_ridgeline_3d()` shows cross-sectional slices:

```{r surface}
ggplot(mountain, aes(x, y, z)) +
      geom_surface_3d(aes(fill = z, color = z)) +
      scale_fill_viridis_c() + scale_color_viridis_c() +
      coord_3d(ratio = c(1.5, 2, 1), expand = FALSE, panels = "zmin",
               light = light(direction = c(1, 0, 0))) +
      guides(fill = guide_colorbar_3d()) +
      theme_light()
```

Stats like `stat_function_3d()`, `stat_smooth_3d()`, and `stat_density_3d()` generate
surface data from functions, model fits, or kernel density estimates. These stats can be
paired with any of the surface geoms. See the [3D surfaces](https://matthewkling.github.io/ggcube/articles/surfaces.html)
article for more detail on surface plotting options.

### Points

`geom_point_3d()` extends scatter plots with depth-scaled point sizes (closer points shown larger), 
proper depth sorting (closer points plotted on top), and optional reference lines and points that 
project onto cube faces:

```{r points}
ggplot(mpg, aes(x = displ, y = hwy, z = drv, fill = class)) +
      geom_point_3d(size = 3, shape = 21, color = "black", stroke = .1,
                    ref_lines = TRUE, ref_faces = c("ymax", "xmax")) +
      coord_3d()
```

### Paths

`geom_path_3d()` connects observations with depth-sorted, depth-scaled line segments:

```{r paths}
x <- seq(0, 20*pi, pi/16)
spiral <- data.frame(x = x, y = sin(x), z = cos(x), time = 1:length(x))
ggplot(spiral, aes(x, y, z, color = time)) +
      geom_path_3d() +
      scale_color_gradientn(colors = c("blue", "purple", "red", "orange")) +
      coord_3d() +
      theme_light()
```

`geom_segment_3d()` is also available for drawing individual segments defined by start
and end coordinates.

### Prisms

`geom_col_3d()` produces 3D column charts, `geom_bar_3d()` creates 3D histograms with
automatic binning, and `geom_voxel_3d()` renders arrays of cubes:

```{r bar}
ggplot(iris, aes(Species, Sepal.Length, fill = Species)) +
      geom_bar_3d(bins = 20, width = c(.5, 1)) +
      coord_3d(scales = "fixed", ratio = c(1, 3, .25), yaw = 60) +
      scale_z_continuous(expand = c(0, 0)) +
      theme(legend.position = "none")
```

### Hulls

`geom_hull_3d()` computes and renders triangulated hulls from 3D point clouds, including convex and alpha hulls:

```{r hull}
ggplot(sphere_points, aes(x, y, z)) +
      geom_hull_3d(method = "convex", fill = "#9e2602", color = "#5e1600") +
      coord_3d()
```

### Distributions

`stat_distributions_3d()` computes 1D kernel density estimates per group and arranges
them as ridgeline surfaces, the 3D analog of `ggridges::geom_density_ridges()`:

```{r distributions}
ggplot(iris, aes(y = Sepal.Length, x = Species, fill = Species)) +
      stat_distributions_3d() +
      scale_z_continuous(expand = expansion(mult = c(0, NA))) +
      coord_3d() +
      theme(legend.position = "none")
```

### Text

`geom_text_3d()` renders text in 3D, either as "billboard" labels that always face the
camera, or as polygon outlines that can be oriented in any direction:

```{r text}
df <- expand.grid(x = c("H", "B"), y = c("a", "o", "u"), z = c("g", "t"))
df$label <- paste0(df$x, df$y, df$z)
ggplot(df, aes(x, y, z, label = label, fill = x)) +
      geom_text_3d(method = "polygon", facing = "zmax",
                   size = 5, weight = "bold") +
      coord_3d(scales = "fixed", light = NULL)
```


## Lighting

Lighting modifies the fill and/or color of polygon faces based on their orientation
relative to a light source, giving surfaces a sense of depth and shape. It's controlled
via the `light()` function, which can be passed to `coord_3d()` (applying to all layers)
or to individual layer functions (overriding the coord-level setting):

```{r lighting}
p <- ggplot(sphere_points, aes(x, y, z)) +
      geom_hull_3d(fill = "#9e2602", color = "#5e1600")

p + coord_3d(light = light(method = "direct", mode = "hsl",
                           direction = c(0, 0, 1)))
```

Use `light = "none"` to disable lighting entirely, or `light = NULL` in a layer to
inherit the coord-level setting. For a comprehensive guide to lighting methods, color
modes, light direction, and backface handling, see the [lighting](https://matthewkling.github.io/ggcube/articles/lighting.html) article.


## Scales, guides, and themes

### Z-axis scales

ggcube provides `scale_z_continuous()` and `scale_z_discrete()` for controlling the
z-axis, with the same interface as their x/y counterparts. `zlim()` is a shorthand for
setting z-axis limits:

```{r zlim, fig.show = "hide"}
ggplot(mtcars, aes(mpg, wt, z = qsec)) +
      geom_point() +
      zlim(15, 20) +
      coord_3d()
```

### Shaded guides

When lighting is active, standard color guides don't reflect the shading visible in the
plot. `guide_colorbar_3d()` and `guide_legend_3d()` create guides that show the range of
shaded colors:

```{r guide, fig.show = "hide"}
ggplot(mountain, aes(x, y, z, fill = z)) +
      stat_surface_3d(light = light(mode = "hsl", direction = c(1, 0, 0))) +
      guides(fill = guide_colorbar_3d()) +
      scale_fill_gradientn(colors = c("tomato", "dodgerblue")) +
      coord_3d()
```

### Panels and themes

The `panels` argument to `coord_3d()` controls which cube faces are drawn. Faces behind
the data are "background" panels; faces in front are "foreground" panels (which default to
semi-transparent so they don't obscure the data):

```{r theme}
ggplot(sphere_points, aes(x, y, z)) +
      geom_hull_3d() +
      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"),
            axis.text.z = element_text(color = "darkred"),
            axis.title = element_text(margin = margin(t = 30)),
            axis.title.x = element_text(color = "magenta"))
```
Standard ggplot2 themes and `theme()` customization work as expected. ggcube adds
foreground-specific elements (`panel.foreground`, `panel.grid.foreground`,
`panel.border.foreground`) and z-axis text elements (`axis.text.z`, `axis.title.z`).
ggcube's `element_rect()` extends ggplot2's version with an `alpha` parameter for
transparency. For details, see the theming section of the [3D coordinates](https://matthewkling.github.io/ggcube/articles/coord_3d.html)
article.


## Annotations

`annotate_3d()` adds reference geometry --- points, text labels, or segments --- to any
3D layer. Unlike adding a separate layer, annotations are embedded within
the host layer so they participate in the same depth sorting:

```{r annotate, eval = FALSE}
summit <- filter(mountain, z == max(z))
ggplot(mountain, aes(x, y, z)) +
      geom_contour_3d(
            annotate = list(
                  annotate_3d("point", x = summit$x, y = summit$y, z = summit$z, color = "red"),
                  annotate_3d("text", x = summit$x, y = summit$y, z = summit$z, color = "red",
                              label = "Summit", fontface = "bold", vjust = -1)
            ), fill = "black"
      ) +
      coord_3d(ratio = c(2, 3, 1.5), light = "none")
```


## Mixing 2D and 3D

`position_on_face()` lets you project layers onto cube faces, enabling a mix of 3D and
2D content. You can flatten a 3D layer onto a face, or place a natively 2D layer (like
`stat_density_2d()`) onto a specific face using the `axes` parameter:

```{r face_projection}
ggplot(iris, aes(Sepal.Length, Sepal.Width, Petal.Length,
                 color = Species, fill = Species)) +
      coord_3d() + xlim(4, 8) +
      stat_density_2d(position = position_on_face(faces = "zmin", axes = c("x", "y")),
                      geom = "polygon", alpha = .1, linewidth = .25) +
      geom_hull_3d(position = position_on_face("ymax"), alpha = .5) +
      geom_point_3d(shape = 21, color = "black", stroke = .25)
```


## Animation

`animate_3d()` creates animated rotations of any ggcube plot. Rotation angles are
specified as keyframe vectors that get interpolated across frames:

```{r animation, eval = FALSE}
p <- ggplot(mountain, aes(x, y, z)) +
      geom_contour_3d(fill = "black", color = "white", linewidth = .5) +
      coord_3d(ratio = c(1.5, 2, 1), light = "none", zoom = 1.5) +
      theme_void()

animate_3d(p, yaw = c(0, 360))
```

Output format is controlled via renderers: `gifski_renderer_3d()` (GIF, the default),
`av_renderer_3d()` (MP4), or `file_renderer_3d()` (individual frames). Use
`anim_save_3d()` to save the result to a file.
