Skip to contents

A ghost, a skeleton and a grim reaper have found their way into ggfoundry. A spider’s web and a gravestone complete a small Halloween set, with the same uneven curves and loose ink lines as the package’s existing shapes.

The five shapes are available in the development version. Their outlines and interiors can be coloured separately, and a transparent fill keeps the drawing visible. That makes them useful for a seasonal plot, a palette experiment, or a suitably spooky annotation.

Try the development version

Install the Dev branch from GitHub, then restart R if ggfoundry is already loaded:

pak::pak("cgoo4/ggfoundry@Dev")
library(ggplot2)
library(ggfoundry)
library(tibble)
library(dplyr)
library(forcats)

halloween <- subset(shapes_cast(), set == "halloween")
halloween$shape
#> [1] "ghost"      "gravestone" "grimreaper" "skeleton"   "spiderweb"

This set is part of development version 0.4.0.9000; the published CRAN version 0.4.0 remains unchanged.

Meet the shapes

The names to use in a plot are skeleton, ghost, spiderweb, grimreaper, and gravestone. The skeleton has a large skull and sparse ribs, the ghost a billowing hem, and the web skew spokes with sagging threads. A crooked hood and sweeping scythe identify the reaper; the gravestone has an uneven arch and hand-lettered inscription.

Here they are with filled interiors and with outlines alone:

shape_names <- c("skeleton", "ghost", "spiderweb", "grimreaper", "gravestone")
shape_labels <- c(
  "Skeleton",
  "Ghost",
  "Spider's web",
  "Grim reaper",
  "Gravestone"
)
ink <- "#36312F"
paper <- "#FAF7F0"

gallery <- tribble(
  ~x , ~shape       , ~label         , ~appearance    , ~fill     ,
   1 , "skeleton"   , "Skeleton"     , "Filled"       , "#DED2B6" ,
   2 , "ghost"      , "Ghost"        , "Filled"       , "#D5DDD3" ,
   3 , "spiderweb"  , "Spider's web" , "Filled"       , "#AFBEB6" ,
   4 , "grimreaper" , "Grim reaper"  , "Filled"       , "#A596B5" ,
   5 , "gravestone" , "Gravestone"   , "Filled"       , "#AEA99C" ,
   1 , "skeleton"   , "Skeleton"     , "Outline only" , NA        ,
   2 , "ghost"      , "Ghost"        , "Outline only" , NA        ,
   3 , "spiderweb"  , "Spider's web" , "Outline only" , NA        ,
   4 , "grimreaper" , "Grim reaper"  , "Outline only" , NA        ,
   5 , "gravestone" , "Gravestone"   , "Outline only" , NA
)

ggplot(gallery, aes(x, 1)) +
  geom_casting(aes(shape = shape, fill = fill), colour = ink, size = 0.56) +
  geom_text(aes(y = 0.28, label = label), colour = ink, size = 3.6) +
  scale_shape_identity() +
  scale_fill_identity(na.value = NA) +
  facet_wrap(~appearance, ncol = 1) +
  coord_fixed(xlim = c(0.35, 5.65), ylim = c(0, 1.7), expand = FALSE) +
  theme_void(base_size = 13) +
  theme(
    strip.text = element_text(hjust = 0, colour = ink, margin = margin(b = 10)),
    panel.spacing = grid::unit(1, "lines"),
    plot.background = element_rect(fill = paper, colour = NA),
    plot.margin = margin(12, 12, 12, 12)
  )
The five Halloween shapes with filled interiors and with fill = NA.

The five Halloween shapes with filled interiors and with fill = NA.

The ghost, gravestone and reaper have broad areas for fill. The skeleton’s bones and the web’s threads have much narrower interiors, so changing their fill produces a subtler effect. Give those two shapes enough room for their details to read.

An imaginary Halloween evening

Suppose a village is planning five Halloween activities. The booking counts below are fictional demonstration data. Each shape identifies an activity, while its horizontal position shows the number of bookings.

events <- tribble(
  ~activity        , ~shape       , ~bookings ,
  "Skeleton dance" , "skeleton"   ,        28 ,
  "Ghost stories"  , "ghost"      ,        42 ,
  "Web workshop"   , "spiderweb"  ,        16 ,
  "Reaper walk"    , "grimreaper" ,        24 ,
  "Graveyard tour" , "gravestone" ,        12
) |>
  arrange(bookings) |>
  mutate(activity = fct_inorder(activity))

ggplot(events, aes(bookings, activity)) +
  geom_segment(
    aes(x = 0, xend = bookings, yend = activity),
    colour = "#DAD1C4",
    linewidth = 0.8
  ) +
  geom_casting(aes(shape = shape, fill = bookings), colour = ink, size = 0.17) +
  geom_text(
    aes(label = bookings),
    nudge_x = 4.5,
    hjust = 0,
    colour = ink,
    size = 4
  ) +
  scale_shape_identity() +
  scale_fill_gradient(low = "#B5C1B2", high = "#D49A65", guide = "none") +
  scale_x_continuous(
    limits = c(0, 52),
    breaks = seq(0, 50, 10),
    expand = expansion(mult = 0)
  ) +
  scale_y_discrete(expand = expansion(add = 0.65)) +
  labs(title = "An imaginary Halloween evening", x = "Bookings", y = NULL) +
  theme_minimal(base_size = 13) +
  theme(
    text = element_text(colour = ink),
    axis.text = element_text(colour = ink),
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_line(colour = "#E5DDD1"),
    plot.background = element_rect(fill = paper, colour = NA),
    panel.background = element_rect(fill = paper, colour = NA),
    plot.title.position = "plot",
    plot.margin = margin(12, 18, 12, 12)
  )
Fictional booking counts. Shape identifies the activity; position and labels show the count.

Fictional booking counts. Shape identifies the activity; position and labels show the count.

The shapes form a nominal set: choose one for each category with scale_shape_manual(), or use scale_shape_identity() when the data already contain their names, as above. Continuous fill is resolved for each observation, so the interior colour can vary along the booking scale.

For a simpler treatment, set fill = NA or fill = "transparent". The outline and its internal marks remain visible. size controls the shape relative to its plotting panel, so review the result at the dimensions at which readers will see it.

The existing pumpkin and jackolantern shapes in the food set make useful companions. A seasonal plot can mix shapes across sets; shapes_cast() keeps their names close to hand.