Intro Thoughts

Status Quo

library(tidyverse)

Experiment

library(ggplot2)
library(patchwork)

# =====================================================================
# BUSINESS CARD SHEET LAYOUT
# Defaults below match the most common perforated sheet layout
# (Avery 5371 / 28371 / 8371 and most "10-up" business card stock):
#   - US Letter page: 8.5 x 11 in
#   - 10 cards per sheet: 2 columns x 5 rows
#   - each card: 3.5 x 2 in
#   - margins: 0.75 in left/right, 0.5 in top/bottom
# If your brand of card stock differs, just change these numbers —
# everything else derives from them.
# =====================================================================

page_width   <- 8.5   # in
page_height  <- 11    # in
card_width   <- 3.5   # in
card_height  <- 2     # in
ncol_cards   <- 2
nrow_cards   <- 5
left_margin  <- 0.75  # in
top_margin   <- 0.5   # in

# sanity check: grid + margins should equal the page exactly
stopifnot(abs((ncol_cards * card_width + 2 * left_margin) - page_width)  < 1e-6)
stopifnot(abs((nrow_cards * card_height + 2 * top_margin) - page_height) < 1e-6)

# =====================================================================
# YOUR SUBPLOTS — replace make_plot() with your real plotting code.
# Each plot should be "clean" (theme_void or minimal theme, no titles
# that will get clipped) since it will be squeezed into a 3.5 x 2 card.
# =====================================================================

make_plot <- function(i) {
  set.seed(i)
  df <- data.frame(x = rnorm(40), y = rnorm(40))
  ggplot(df, aes(x, y)) +
    geom_point(size = 1, color = "steelblue") +
    theme_void() +
    theme(plot.margin = margin(4, 4, 4, 4)) +
    labs(title = paste("Card", i)) +
    theme(plot.title = element_text(size = 8, hjust = 0.5))
}

n_cards <- ncol_cards * nrow_cards
plots <- lapply(seq_len(n_cards), make_plot)

# =====================================================================
# ARRANGE WITH PATCHWORK
# =====================================================================

grid <- wrap_plots(plots, ncol = ncol_cards, nrow = nrow_cards)

# Push the whole grid into the page at the exact perforation margins.
# Because ggplot/grid honor absolute units ("in"), this stays pixel-
# perfect regardless of the output device size, as long as you save
# at exactly page_width x page_height below.
grid <- grid + plot_annotation(
  theme = theme(
    plot.margin = margin(
      t = top_margin, r = left_margin,
      b = top_margin, l = left_margin,
      unit = "in"
    )
  )
)

# =====================================================================
# OPTIONAL: draw faint card outlines so you can test alignment on
# plain paper before committing to real perforated stock. Comment
# this block out once you've confirmed alignment.
# =====================================================================

grid <- grid & theme(
  plot.background = element_rect(colour = "grey80", linewidth = 0.3, fill = NA)
)

# =====================================================================
# SAVE — width/height MUST equal page_width/page_height exactly.
# =====================================================================

ggsave(
  filename = "business_card_plots.pdf",
  plot = grid,
  width = page_width,
  height = page_height,
  units = "in",
  device = cairo_pdf   # keeps vector output crisp; falls back to pdf() if unavailable
)
## Warning in grSoftVersion(): unable to load shared object '/Library/Frameworks/R.framework/Resources/modules//R_X11.so':
##   dlopen(/Library/Frameworks/R.framework/Resources/modules//R_X11.so, 0x0006): Library not loaded: /opt/X11/lib/libSM.6.dylib
##   Referenced from: <7ECC4104-EC6A-38FD-9BEA-BFE0B870925C> /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/modules/R_X11.so
##   Reason: tried: '/opt/X11/lib/libSM.6.dylib' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e' or 'arm64e.v1' or 'arm64' or 'arm64')), '/System/Volumes/Preboot/Cryptexes/OS/opt/X11/lib/libSM.6.dylib' (no such file), '/opt/X11/lib/libSM.6.dylib' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e' or 'arm64e.v1' or 'arm64' or 'arm64')), '/Library/Frameworks/R.framework/Resources/lib/libSM.6.dylib' (no such file), '/Library/Java/JavaVirtualMachines/jdk-11.0.18+10/Contents/Home/lib/server/libSM.6.dylib' (no such file)
## Warning in cairoVersion(): unable to load shared object '/Library/Frameworks/R.framework/Resources/library/grDevices/libs//cairo.so':
##   dlopen(/Library/Frameworks/R.framework/Resources/library/grDevices/libs//cairo.so, 0x0006): Library not loaded: /opt/X11/lib/libXrender.1.dylib
##   Referenced from: <263B701F-2238-3478-A7D2-B7513231EF98> /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library/grDevices/libs/cairo.so
##   Reason: tried: '/opt/X11/lib/libXrender.1.dylib' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e' or 'arm64e.v1' or 'arm64' or 'arm64')), '/System/Volumes/Preboot/Cryptexes/OS/opt/X11/lib/libXrender.1.dylib' (no such file), '/opt/X11/lib/libXrender.1.dylib' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e' or 'arm64e.v1' or 'arm64' or 'arm64')), '/Library/Frameworks/R.framework/Resources/lib/libXrender.1.dylib' (no such file), '/Library/Java/JavaVirtualMachines/jdk-11.0.18+10/Contents/Home/lib/server/libXrender.1.dylib' (no [... truncated]
## Warning in device(filename = "business_card_plots.pdf", width = 8.5, height =
## 11, : failed to load cairo DLL
# Tip: print this PDF at 100% scale / "actual size" (NOT "fit to page")
# or the cards will drift off the perforations.

Closing remarks, Other Relevant Work, Caveats