Search code examples
rggplot2grid

ggplot2/grid: keep segments grob arrow same scale regardless of length


I am adding arrows to my ggplot using grid::segmentsGrob(arrow). I define the position of the arrow with x-axis coordinates. For some reason, the height of the arrow scales based on the length of the arrow. I want the arrows to occupy the same height, and only shorten the length of the arrow. Am I missing a height parameter?

library(ggplot2)
library(grid)

df <- data.frame(
  Position=(c(1:5000)),
  Signal=(c((rep(c(5),times=2000)), (rep(c(100),times=1000)), (rep(c(5),times=2000))))
  )


Plotv1 <- ggplot()+
  geom_line(data = df, aes(x=Position, y=Signal, col = "#000000"))+
  coord_cartesian(clip="off") +
  theme(axis.text.x = element_blank()) +
  theme(
    axis.title.x = element_text(margin=margin(t=30)),
    legend.title = element_text(colour = "#000000", size=12),
    legend.text = element_text(colour = "#000000", size=12)
  ) +
  guides(fill = "none") +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-0.3, "npc"),
      y1 = unit(-0.3, "npc"),
      arrow = arrow(angle=45, length = unit(.15, 'npc')),
      gp = grid::gpar(lwd=3, fill = "#000000")
    ),
    xmin = 1,
    xmax = 1700
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-0.3, "npc"),
      y1 = unit(-0.3, "npc"),
      arrow = arrow(angle=45, length = unit(.15, 'npc')),
      gp = grid::gpar(lwd=3, fill = "#000000")
    ),
    xmin = 2000,
    xmax = 2200
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-0.3, "npc"),
      y1 = unit(-0.3, "npc"),
      arrow = arrow(angle=45, length = unit(.15, 'npc')),
      gp = grid::gpar(lwd=3, fill = "#000000")
    ),
    xmin = 2500,
    xmax = 5000
  )

ggsave(paste("~/Desktop/Plotv1.png", sep = ""), Plotv1, width = 8, height = 1.7)

Plotv1.png


Solution

  • The issue is that you specified the length of the arrow in "npc" units aka normalized parent coordinates. As a result the length of your arrows will differ as the absolute width and height of the parent viewport differ, i.e. in your case the viewport for each arrow is the rectangle defined by xmin and xmax. Instead, use absolute units for the length of your arrows. Additionally, I would suggest to use absolute units to position the arrows, too.

    library(ggplot2)
    library(grid)
    
    df <- data.frame(
      Position = 1:5000,
      Signal = c(
        rep(5, times = 2000),
        rep(100, times = 1000),
        rep(5, times = 2000)
      )
    )
    
    segment <- grid::segmentsGrob(
      y0 = unit(-10, "pt"),
      y1 = unit(-10, "pt"),
      arrow = arrow(angle = 45, length = unit(.3, "cm")),
      gp = grid::gpar(lwd = 3, fill = "#000000")
    )
    
    ggplot() +
      geom_line(data = df, aes(x = Position, y = Signal, col = "#000000")) +
      coord_cartesian(clip = "off") +
      theme(axis.text.x = element_blank()) +
      theme(
        axis.title.x = element_text(margin = margin(t = 30)),
        legend.title = element_text(colour = "#000000", size = 12),
        legend.text = element_text(colour = "#000000", size = 12)
      ) +
      guides(fill = "none") +
      annotation_custom(
        segment, xmin = 1, xmax = 1700
      ) +
      annotation_custom(
        segment, xmin = 2000, xmax = 2200
      ) +
      annotation_custom(
        segment, xmin = 2500, xmax = 5000
      )
    

    enter image description here