0

I make plots with an annotation, currently defined by absolute values. This causes issues when the Y-axis scale changes: the annotation gets squished/expanded. I am making many of these plots within a for loop so manually adjusting the values isn't feasible.

Is there a way for me to make the size of the annotation constant, perhaps relative to the image output size? Alternatively, relative to y-axis range? If using the axis range, keep in mind that two datasets are plotted so finding he maximum range between the two would be necessary. Thanks!

Simplified example below. Swap c_1 and t_1 for other df columns to generate Plot2/3

library(ggplot2)

c_1 <- c(rep(c(5),times=1000), rep(c(10),times=50), rep(c(5),times=1000))
t_1 <- c(rep(c(5),times=1000), rep(c(15),times=50), rep(c(5),times=1000))
c_2 <- c(rep(c(5),times=1000), rep(c(75),times=50), rep(c(5),times=1000))
t_2 <- c(rep(c(5),times=1000), rep(c(68),times=50), rep(c(5),times=1000))
c_3 <- c(rep(c(5),times=1000), rep(c(150),times=50), rep(c(5),times=1000))
t_3 <- c(rep(c(5),times=1000), rep(c(140),times=50), rep(c(5),times=1000))
df <- data.frame(position = 1:length(c_1), c_1, t_1, c_2, t_2, c_3,t_3)

Plot1 <- ggplot()+
  geom_hline(yintercept = -10, size =0.5, col = "#333333") +
  annotate(geom = "rect", ymin = -20.5, max = -0.5, xmin = 525, xmax = 1525, fill = "#333333")+
  annotate(geom = "text", y = -10, x = median(1:2050), label = "My_Text", col = "white", size = 5, fontface = 'italic') +
  geom_line(data = df, aes(x=position, y=c_1), size = .75, alpha = 0.7)+
  geom_line(data = df, aes(x=position, y=t_1,), size = .75, alpha = 0.7)+
  ggtitle("Plot 1")

ggsave("~/Desktop/Plot1.png", Plot1, width = 8, height = 1.7)

Plot1 Plot2 Plot3

JVGen
  • 401
  • 3
  • 10

1 Answers1

3

One option to get annotations of constant size independent of the scale of the data would be to use annotation_custom which allows to set the position, height and width in relative units of the plot area:

library(ggplot2)

plot_fun <- function(y1, y2, title) {
  ggplot() +
    geom_hline(yintercept = -10, size = 0.5, col = "#333333") +
    annotation_custom(
      grid::rectGrob(
        x = unit(0.5, "npc"),
        y = unit(.125, "npc") + unit(1, "mm"),
        width = unit(.5, "npc"),
        height = unit(.25, "npc"),
        gp = grid::gpar(fill = "#333333")
      )
    ) +
    annotation_custom(
      grid::textGrob(
        label = "My_Text",
        x = unit(0.5, "npc"),
        y = unit(0.125, "npc") + unit(1, "mm"),
        gp = grid::gpar(col = "white", fontface = "italic", fontsize = 5 * .pt)
      )
    ) +
    geom_line(data = df, aes(x = position, y = .data[[y1]]), size = .75, alpha = 0.7) +
    geom_line(data = df, aes(x = position, y = .data[[y2]]), size = .75, alpha = 0.7) +
    ggtitle(title)
}

dat <- data.frame(
  y1 = paste0("c_", 1:3),
  y2 = paste0("t_", 1:3),
  title = paste("Plot", 1:3)
)

purrr::pmap(dat, plot_fun) |>
  patchwork::wrap_plots(ncol = 1)

enter image description here

EDIT If you want to position the annotations on the x scale using data coordinates you could:

plot_fun <- function(y1, y2, title) {
  ggplot() +
    geom_hline(yintercept = -10, size = 0.5, col = "#333333") +
    annotation_custom(
      grid::rectGrob(
        y = unit(.125, "npc") + unit(1, "mm"),
        width = unit(1, "npc"),
        height = unit(.25, "npc"),
        gp = grid::gpar(fill = "#333333")
      ),
      xmin = 525, xmax = 1525,
    ) +
    annotation_custom(
      grid::textGrob(
        label = "My_Text",
        hjust = .5,
        x = unit(0, "npc"),
        y = unit(0.125, "npc") + unit(1, "mm"),
        gp = grid::gpar(col = "white", fontface = "italic", fontsize = 5 * .pt)
      ),
      xmin = median(1:2050)
    ) +
    geom_line(data = df, aes(x = position, y = .data[[y1]]), size = .75, alpha = 0.7) +
    geom_line(data = df, aes(x = position, y = .data[[y2]]), size = .75, alpha = 0.7) +
    ggtitle(title)
}
stefan
  • 90,330
  • 6
  • 25
  • 51
  • Thank you for your response. Two questions: 1) Is there a way to expand the y-axis appropriately so that the annotation can always fit between the plotted data and the values on the x-axis? 2) In the rectGrob() is it possible to keep the x-axis designations related to the data (instead of npc units)? – JVGen Aug 22 '23 at 15:28
  • Concerning your second question. Yes, this is possible. See my edit which uses the values from your original code. Concerning question 1. Probably yes, but requires some effort to compute the amount by which the scale has to be expanded to make room for the labels. See https://stackoverflow.com/questions/75098826/automatically-leave-enough-room-for-a-label-next-to-a-barplot/75099898#75099898 for a possible approach. – stefan Aug 22 '23 at 16:37
  • Another option would be to move the rectangle so it falls below the x-axis and above the a-axis title. I tried adding an X-axis title margin to make white space for rectangle, but I could find a way to move it below the X-axis – JVGen Aug 22 '23 at 23:58