Skip to contents

Why this vignette

grrr grab objects are S7 classes:

  • grab_atom: a leaf node with one shape type and a tibble-like @data
  • grab_bag: an interior node with @children (which can be atoms or bags)

A useful mental model is:

  • manipulate rows/columns at the grab_atom@data level
  • rebuild or recursively transform atoms inside grab_bag

This vignette focuses on that workflow with manually hard-coded examples.

All examples below use plotting-stage columns (for example x, y, size, fill, colour, label). A single technical key (point_key) is kept only to demonstrate join-style post-processing.

1) Hard-coded grab_atom examples

pts_atom <- grab_atom(
  "point",
  dfr(
    point_key = c("p1", "p2", "p3", "p4"),
    x = c(0.15, 0.35, 0.60, 0.85),
    y = c(0.80, 0.45, 0.55, 0.25),
    size = c(5, 8, 6, 10),
    alpha = c(0.9, 0.6, 0.8, 0.7),
    fill = c("#4C78A8", "#4C78A8", "#4C78A8", "#4C78A8"),
    colour = "#1F2937",
    stroke = 0.3
  )
)

seg_atom <- grab_atom(
  "segment",
  dfr(
    x = c(0.1, 0.1),
    y = c(0.1, 0.1),
    xend = c(0.9, 0.1),
    yend = c(0.1, 0.9),
    colour = c("#9CA3AF", "#9CA3AF"),
    linewidth = c(0.35, 0.35),
    linetype = c("solid", "solid")
  )
)

tibble::as_tibble(pts_atom@data)
#> # A tibble: 4 × 8
#>   point_key     x     y  size alpha fill    colour  stroke
#>   <chr>     <dbl> <dbl> <dbl> <dbl> <chr>   <chr>    <dbl>
#> 1 p1         0.15  0.8      5   0.9 #4C78A8 #1F2937    0.3
#> 2 p2         0.35  0.45     8   0.6 #4C78A8 #1F2937    0.3
#> 3 p3         0.6   0.55     6   0.8 #4C78A8 #1F2937    0.3
#> 4 p4         0.85  0.25    10   0.7 #4C78A8 #1F2937    0.3
tibble::as_tibble(seg_atom@data)
#> # A tibble: 2 × 7
#>       x     y  xend  yend colour  linewidth linetype
#>   <dbl> <dbl> <dbl> <dbl> <chr>       <dbl> <chr>   
#> 1   0.1   0.1   0.9   0.1 #9CA3AF      0.35 solid   
#> 2   0.1   0.1   0.1   0.9 #9CA3AF      0.35 solid

Accessing S7 slots

class(pts_atom)
#> [1] "grrr::grab_atom" "S7_object"
pts_atom@shape
#> [1] "point"
names(pts_atom@data)
#> [1] "point_key" "x"         "y"         "size"      "alpha"     "fill"     
#> [7] "colour"    "stroke"

2) Subsetting rows in grab_atom

A common operation is to subset rows while preserving S7 structure.

# Keep only smaller symbols
light_pts <- grab_filter(pts_atom, size < 9)

nrow(pts_atom@data)
#> [1] 4
nrow(light_pts@data)
#> [1] 3
light_pts@data[, c("point_key", "size", "alpha")]
#> # A tibble: 3 × 3
#>   point_key  size alpha
#>   <chr>     <dbl> <dbl>
#> 1 p1            5   0.9
#> 2 p2            8   0.6
#> 3 p3            6   0.8

You can chain additional tibble-like column operations naturally.

light_pts2 <- grab_mutate(light_pts, alpha_size = alpha * size)
light_pts2 <- grab_select(light_pts2, point_key, x, y, size, alpha, alpha_size)
light_pts2@data
#> # A tibble: 3 × 6
#>   point_key     x     y  size alpha alpha_size
#>   <chr>     <dbl> <dbl> <dbl> <dbl>      <dbl>
#> 1 p1         0.15  0.8      5   0.9        4.5
#> 2 p2         0.35  0.45     8   0.6        4.8
#> 3 p3         0.6   0.55     6   0.8        4.8

3) Joining metadata into grab_atom

grab_left_join() brings lookup metadata directly into atom data.

class_key <- data.frame(
  point_key = c("p1", "p2", "p3", "p4"),
  class = c("cool", "warm", "cool", "warm"),
  fill_new = c("#2A9D8F", "#E76F51", "#2A9D8F", "#E76F51"),
  stringsAsFactors = FALSE
)

pts_joined <- grab_left_join(pts_atom, class_key, by = "point_key")
pts_joined <- grab_mutate(pts_joined, fill = fill_new)
pts_joined <- grab_select(pts_joined, -fill_new)
pts_joined@data[, c("point_key", "class", "fill")]
#> # A tibble: 4 × 3
#>   point_key class fill   
#>   <chr>     <chr> <chr>  
#> 1 p1        cool  #2A9D8F
#> 2 p2        warm  #E76F51
#> 3 p3        cool  #2A9D8F
#> 4 p4        warm  #E76F51

4) Hard-coded grab_bag examples

Now we manually build a nested scene-like bag from atoms.

bg_atom <- grab_atom(
  "rect",
  dfr(xmin = 0, ymin = 0, xmax = 1, ymax = 1, fill = "#F8FAFC", colour = "#CBD5E1", linewidth = 0.3)
)

label_atom <- grab_atom(
  "text",
  dfr(
    x = pts_atom@data$x,
    y = pts_atom@data$y + 0.04,
    label = pts_atom@data$point_key,
    size = 3.5,
    colour = "#334155",
    hjust = 0.5,
    vjust = 0.5
  )
)

annotation_bag <- grab_bag(
  name = "annotation",
  children = list(
    grab_atom("text", dfr(x = 0.5, y = 0.95, label = "Hard-coded grab_bag", size = 4, colour = "#0F172A", hjust = 0.5, vjust = 1))
  )
)

plot_bag <- grab_bag(
  name = "plot-bag",
  children = list(bg_atom, seg_atom, pts_atom, label_atom, annotation_bag)
)

data.frame(
  bag_name = plot_bag@name,
  n_children = length(plot_bag@children),
  stringsAsFactors = FALSE
)
#>   bag_name n_children
#> 1 plot-bag          5

Visual: manually hard-coded grab_bag

emit_grab_preview(plot_bag, "Base hard-coded bag with background, guides, points, labels, and annotation")
p1p2p3p4Hard-coded grab_bag
Base hard-coded bag with background, guides, points, labels, and annotation

5) Recursive transformations for grab_bag

Utilities also recurse through nested grab_bag trees.

# Example: keep only points with size < 9, leave other atoms unchanged.
plot_bag_light <- grab_filter(plot_bag, size < 9)

orig_n <- nrow(plot_bag@children[[3]]@data)
new_n <- nrow(plot_bag_light@children[[3]]@data)
c(orig_points = orig_n, light_points = new_n)
#>  orig_points light_points 
#>            4            3
emit_grab_preview(plot_bag_light, "After recursive filtering: only point rows with size < 9 remain")
p1p2p3p4Hard-coded grab_bag
After recursive filtering: only point rows with size < 9 remain

Recursive join update

plot_bag_colored <- grab_left_join(plot_bag, class_key, by = "point_key")
plot_bag_colored <- grab_map_atoms(plot_bag_colored, function(atom) {
  if (!"fill_new" %in% names(atom@data)) return(atom)
  atom <- grab_mutate(atom, fill = fill_new)
  grab_select(atom, -fill_new)
})

plot_bag_colored@children[[3]]@data[, c("point_key", "class", "fill")]
#> # A tibble: 4 × 3
#>   point_key class fill   
#>   <chr>     <chr> <chr>  
#> 1 p1        cool  #2A9D8F
#> 2 p2        warm  #E76F51
#> 3 p3        cool  #2A9D8F
#> 4 p4        warm  #E76F51
emit_grab_preview(plot_bag_colored, "After recursive join update: point colors mapped from joined class metadata")
p1p2p3p4Hard-coded grab_bag
After recursive join update: point colors mapped from joined class metadata

6) Summary pattern

For data manipulation with grabs:

  1. Start with manually hard-coded atoms/bags so structure is explicit.
  2. Use grab_filter(), grab_mutate(), grab_select(), and grab_left_join() for atom-level data manipulation.
  3. Apply the same verbs recursively to bags (default behavior) so nested scene trees update consistently.
  4. Use grab_map_atoms() when you need custom atom-wise logic beyond the built-in verbs.

That pattern gives you predictable, testable scene edits while keeping all S7 structure intact.