Skip to contents

Every sketch geom shares four parameters that shape the hand-drawn look: roughness, bowing, n_passes, and seed. They are layer parameters (not aesthetics), so you pass them to the geom_sketch_*() call.

roughness

How far points are displaced. 0 is ruler-straight; the default is 1; values above ~3 get loose and scribbly.

x  <- seq(0, 10, length.out = 50)
d  <- data.frame(x = x, y = sin(x))

ggplot(d, aes(x, y)) +
  geom_sketch_line(aes(colour = "0.0  (straight)"), roughness = 0,   seed = 2) +
  geom_sketch_line(aes(colour = "1.0  (default)"),  roughness = 1,   seed = 2) +
  geom_sketch_line(aes(colour = "2.5"),             roughness = 2.5, seed = 2) +
  geom_sketch_line(aes(colour = "5.0  (loose)"),    roughness = 5,   seed = 2) +
  scale_colour_brewer("roughness", palette = "Set1") +
  labs(title = "The roughness dial") +
  theme_sketch()

On bars:

df <- data.frame(x = c("A", "B", "C"), y = c(4, 6, 3))
for (r in c(0, 1, 3)) {
  print(
    ggplot(df, aes(x, y)) +
      geom_sketch_col(fill = "#7BAFD4", roughness = r, seed = 1L) +
      labs(subtitle = paste("roughness =", r), x = NULL) +
      theme_sketch(base_size = 9)
  )
}

bowing

How much line segments bow (curve) between their endpoints. Higher values give a springier, more relaxed stroke.

seg <- data.frame(x = 1, y = 1, xend = 6, yend = 4)
for (b in c(0, 2, 5)) {
  print(
    ggplot(seg) +
      geom_sketch_segment(aes(x = x, y = y, xend = xend, yend = yend),
                          bowing = b, roughness = 0.6, linewidth = 1,
                          seed = 7L) +
      labs(subtitle = paste("bowing =", b)) +
      theme_sketch(base_size = 9)
  )
}

n_passes

How many overlaid strokes to draw. 2 (the default) gives the classic “double-stroke” sketch look; 1 is a single pass; 3+ looks heavily worked.

for (n in c(1, 2, 4)) {
  print(
    ggplot(d, aes(x, y)) +
      geom_sketch_line(n_passes = n, roughness = 1.5, linewidth = 0.7,
                       colour = "grey20", seed = 3L) +
      labs(subtitle = paste("n_passes =", n)) +
      theme_sketch(base_size = 9)
  )
}

medium (line geoms)

The line family — geom_sketch_line(), geom_sketch_path(), geom_sketch_segment(), geom_sketch_step() — also takes a medium, which sets how the stroke is laid down: a tapering pencil, a wet ink line, a crisp fountain pen, a thin ballpoint, a broad brush, charcoal, soft pastel, dusty chalk (best on paper = "chalkboard"), marker, a translucent highlighter band, or crayon. sketch_media() lists them.

for (m in c("pencil", "ink", "brush", "charcoal")) {
  print(
    ggplot(d, aes(x, y)) +
      geom_sketch_line(medium = m, linewidth = 1.2, seed = 2L) +
      labs(subtitle = paste("medium =", m)) +
      theme_sketch(base_size = 9)
  )
}

You can also map medium to a variable with scale_medium_discrete() (see the gallery). The default, "pen", reproduces the classic constant-width sketch line, so existing plots are unchanged.

Drift and fatigue — a tiring hand

roughness and bowing wobble each stroke the same way from end to end. A real hand does two more things: over a long stroke it drifts, wandering gently off course and back, and as it works it tires, so a stroke that starts steady grows shakier as it goes. sketch_tremor() adds both. It is a session option rather than a layer parameter, so it reaches every roughened stroke at once: lines, the outlines of bars, shapes and circles, arrows, and the hand-drawn frame.

d3 <- data.frame(x = 1:6, y = c(3, 5, 2, 6, 4, 7))
hand <- ggplot(d3, aes(x, y)) +
  geom_sketch_col(fill = "#7BAFD4", seed = 1L) +
  geom_sketch_line(linewidth = 0.8, seed = 2L) +
  theme_sketch(rough_frame = TRUE)

old <- options(ggsketch.tremor = sketch_tremor(drift = 1.5))
hand + labs(subtitle = "drift = 1.5: long strokes wander")

options(ggsketch.tremor = sketch_tremor(fatigue = 1.5))
hand + labs(subtitle = "fatigue = 1.5: strokes grow shaky")

options(old)

Drift leaves the two ends of each stroke where they were, so arrows still reach their targets and outlines still close, and it barely moves short strokes. A line through many points is one long stroke, so its middle wanders too, by a little more than roughness alone moves it. Fatigue builds over a stroke’s first four inches, so long outlines finish shakier than short ones. Both scale with roughness (a roughness = 0 layer stays clean) and are seeded, so a figure redraws the same way. Fills, points and text are left as they are. The option is unset by default, and then nothing changes; restore it with options(old) as above, or scope it with withr::with_options(list(ggsketch.tremor = sketch_tremor(drift = 1)), print(p)).

seed — reproducible wobble

The sketch is random, but seeded. A given seed always produces the same drawing, so figures are reproducible across renders and devices. Change the seed for a fresh “hand”.

d2 <- data.frame(x = 1:8, y = c(2, 5, 3, 8, 6, 9, 7, 10))
for (s in c(1, 1, 42)) {
  print(
    ggplot(d2, aes(x, y)) +
      geom_sketch_line(linewidth = 1, seed = s) +
      geom_sketch_point(size = 3, seed = s + 100) +
      labs(subtitle = paste("seed =", s)) +
      theme_sketch(base_size = 9)
  )
}

The first two panels are identical (same seed); the third differs.

Session-wide default

If you do not pass seed, geoms use getOption("ggsketch.seed", 1L). Set it once to make a whole document reproducible:

options(ggsketch.seed = 1L)

ggsketch never touches your global random state: each randomized routine draws from its own seeded stream and restores .Random.seed afterward, so it will not interfere with set.seed() elsewhere in your analysis.