ggplot: Add annotations using separate data above faceted chart

311 Views Asked by At

I'm trying to add set of markers with text above the top of a faceted chart to indicate certain points of interest in the value of x. Its important that they appear in the right position left to right (as per the main scale), including when the overall ggplot changes size.

Something like this...

enter image description here

However, I'm struggling to:

  • place it in the right vertical position (above the facets). In my reprex below (a simplified version of the original), I tried using a value of the factor (Merc450 SLC), but this causes issues such as adding that to every facet including when it is not part of that facet and doesn't actually go high enough. I also tried converting the factor to a number using as.integer, but this causes every facet to include all factor values, when they obviously shouldn't
  • apply to the chart as a whole, not each facet

Note that in the full solution, the marker x values are independent of the main data.

I have tried using cowplot to draw it separately and overlay it, but that seems to: affect the overall scale of the main plot, with the facet titles on the right being cropped is not reliable in placing the markers at the exact location along the x scale

Any pointers welcome.

enter image description here

library(tidyverse)
                
mtcars2 <- rownames_to_column(mtcars, var = "car") %>%
  mutate(make = stringr::word(car, 1)) %>%
  filter(make >= "m" & make < "n")
markers <- data.frame(x = c(max(mtcars2$mpg), rep(runif(nrow(mtcars2), 1, max(mtcars2$mpg))), max(mtcars2$mpg))) %>%
  mutate(name = paste0("marker @ ", round(x)))


ggplot(mtcars2, aes()) +
  # Main Plot
  geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
  # Add Markers
  geom_point(data = markers, aes(x = x, y = "Merc450 SLC"), color = "red") +
  # Marker Labels
  geom_text(data = markers, aes(x = x, "Merc450 SLC",label = name), angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.15) +
  facet_grid(make ~ ., scales = "free", space = "free") +
  theme_minimal() +
  theme(
    # Facets
    strip.background = element_rect(fill="Gray90", color = "white"),
    panel.background = element_rect(fill="Gray95", color = "white"),
    panel.spacing.y = unit(.7, "lines"),
    plot.margin = margin(50, 20, 20, 20)
  )
2

There are 2 best solutions below

1
On BEST ANSWER

Perhaps draw two separate plots and assemble them together with patchwork:

library(patchwork) 

p1 <- ggplot(markers, aes(x = x,  y = 0)) +
    geom_point(color = 'red') +
    geom_text(aes(label = name),
        angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.02) +
    scale_y_continuous(limits = c(-0.01, 0.15), expand = c(0, 0)) +
    theme_minimal() +
    theme(axis.text = element_blank(),
        axis.title = element_blank(),
        panel.grid = element_blank())

p2 <- ggplot(mtcars2, aes(x = mpg, y = car, fill = cyl)) +
    geom_tile(color = "white") +
    facet_grid(make ~ ., scales = "free", space = "free") +
    theme_minimal() +
    theme(
        strip.background = element_rect(fill="Gray90", color = "white"),
        panel.background = element_rect(fill="Gray95", color = "white"),
        panel.spacing.y = unit(.7, "lines")
    )

p1/p2 + plot_layout(heights = c(1, 9))

enter image description here

1
On

It required some workaround with plot on different plot and using cowplot alignment function to align them on the same axis. Here is a solution

library(tidyverse)
library(cowplot)

# define a common x_axis to ensure that the plot are on same scales
# This may not needed as cowplot algin_plots also adjust the scale however
# I tended to do this extra step to ensure.
x_axis_common <- c(min(mtcars2$mpg, markers$x) * .8,
  max(mtcars2$mpg, markers$x) * 1.1)

# Plot contain only marker
plot_marker <- ggplot() +
  geom_point(data = markers, aes(x = x, y = 0), color = "red") +
  # Marker Labels
  geom_text(data = markers, aes(x = x, y = 0,label = name),
    angle = 45, size = 2.5, hjust=0, nudge_x = 0, nudge_y = 0.001) +
  # using coord_cartesian to set the zone of plot for some scales
  coord_cartesian(xlim = x_axis_common,
    ylim = c(-0.005, 0.03), expand = FALSE) +
  # using theme_nothing from cow_plot which remove all element
  # except the drawing
  theme_nothing()

# main plot with facet
main_plot <- ggplot(mtcars2, aes()) +
  # Main Plot
  geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
  coord_cartesian(xlim = x_axis_common, expand = FALSE) +
  # Add Markers
  facet_grid(make ~ ., scales = "free_y", space = "free") +
  theme_minimal() +
  theme(
    # Facets
    strip.background = element_rect(fill="Gray90", color = "white"),
    panel.background = element_rect(fill="Gray95", color = "white"),
    panel.spacing.y = unit(.7, "lines"),
    plot.margin = margin(0, 20, 20, 20)
  )

Then align the plot and plot them using cow_plot

# align the plots together
temp <- align_plots(plot_marker, main_plot, axis = "rl",
  align = "hv")

# plot them with plot_grid also from cowplot - using rel_heights for some
# adjustment
plot_grid(temp[[1]], temp[[2]], ncol = 1, rel_heights = c(1, 8))

Created on 2021-05-03 by the reprex package (v2.0.0)