Which pch character do I need for {ggplot2}?

A visual reference for all 26 pch point shapes in ggplot2, showing which use outline colour, fill colour, or both.
R
dataviz
Published

May 25, 2022

When using geom_point() in {ggplot2} you can control the shape of each point with the shape (or pch) aesthetic. But which numbers give you which shapes — and crucially, which respond to color vs fill?

There are 26 shapes (0–25), split into three groups:

pch_tib <- tibble(
  pch_number = 0:25,
  pch_description = c(rep("Outline", 15), rep("Fill", 6), rep("Both", 5))
) |>
  mutate(
    pch_description = as_factor(pch_description),
    pch_description = fct_relevel(pch_description, c("Outline", "Fill", "Both"))
  )

pch_outline <- pch_tib |>
  filter(pch_description == "Outline") |>
  mutate(x = rep(1:5, 3), y = rev(sort(rep(c("a", "b", "c"), 5))))

pch_fill <- pch_tib |>
  filter(pch_description == "Fill") |>
  mutate(x = 1:6, y = rep("d", 6))

pch_both <- pch_tib |>
  filter(pch_description == "Both") |>
  mutate(x = 1:5, y = rep("e", 5))

Fixed vs free facet spacing

The charts below use facet_grid() to group the shapes by type. The space argument controls how the panel heights relate to each other.

space = “fixed”

With space = "fixed" each row of the grid takes up equal height regardless of how many points it contains.

pch_outline |>
  bind_rows(pch_fill, pch_both) |>
  ggplot(aes(x = x, y = y, pch = pch_number)) +
  geom_point(fill = "lightskyblue3", color = "black", size = 5) +
  geom_text(aes(label = pch_number), nudge_y = 0.2) +
  facet_grid(
    pch_description ~ .,
    scales = "free_y",
    switch = "y",
    space = "fixed"
  ) +
  scale_shape_identity() +
  labs(title = 'Using facet_grid(space = "fixed")') +
  theme_ipsum() +
  theme(
    strip.text.y.left = element_text(angle = 0),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.text.y = element_blank(),
    axis.text.x = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
  )

space = “free”

With space = "free" each row height is proportional to the number of distinct y values it contains, so groups with fewer shapes get less vertical space.

pch_outline |>
  bind_rows(pch_fill, pch_both) |>
  ggplot(aes(x = x, y = y, pch = pch_number)) +
  geom_point(fill = "lightskyblue3", color = "black", size = 5) +
  geom_text(aes(label = pch_number), nudge_y = 0.4) +
  facet_grid(
    pch_description ~ .,
    scales = "free_y",
    switch = "y",
    space = "free"
  ) +
  scale_shape_identity() +
  labs(title = 'Using facet_grid(space = "free")') +
  theme_ipsum() +
  theme(
    strip.text.y.left = element_text(angle = 0),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.text.y = element_blank(),
    axis.text.x = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
  )

This chart is a rebuild of the excellent reference at blog.albertkuo.me.