ggplot2ggrepel

How to horizontally align repelled labels for overlapping points


Data:

df <- structure(list(interval = c("1990-1994", "1995-1999", "2000-2004", 
"2005-2009", "2010-2014"), G = c(82.2047540759008, 66.8830947511929, 
60.8452885555918, 60.815015015015, 56.9713761985336), I = c(0.0816584033395321, 
0.0878634449241925, 0.0880830474237161, 0.0808069481620484, 0.0871282403928035
)), row.names = c(NA, -5L), class = "data.frame")

Code:

library(ggplot2)
library(ggrepel)

#Set label text size
ts=6

#Define theme
pt <- theme(panel.grid.major=element_blank(), panel.grid.minor=element_blank(), plot.title=element_blank(), 
            panel.background=element_blank(), panel.border=element_rect(fill=NA, colour="black"), 
            axis.line=element_line(colour="black"), axis.title=element_blank(), axis.ticks = element_blank(), 
            axis.text=element_blank(), aspect.ratio=1)

#Generate plot
ggplot(df, aes(x=I, y=G/100, label=interval)) + geom_point() + 
  geom_text_repel(aes(label=interval), size=ts, direction="both", min.segment.length=Inf, nudge_x=.06, hjust=0) + 
  scale_x_continuous(limits=c(-1,1), breaks=c(-1,0,1), labels=c("", 0, ""), expand=c(0,0)) + 
  scale_y_continuous(limits=c(0,1), breaks=c(0,.5,1), expand=c(0,0)) + 
  geom_hline(yintercept=0.5) + geom_vline(xintercept=0) + 
  annotate("text", size=ts, x=0.02, y=0.03, label="0", hjust=0) + 
  annotate("text", size=ts, x=0.97, y=0.03, label="I", hjust=1, fontface="italic") + 
  annotate("text", size=ts, x=0.02, y=0.47, label="0.5", hjust=0) + 
  annotate("text", size=ts, x=0.02, y=0.97, label="G", hjust=0, fontface="italic") + pt

Result: enter image description here

The default placement of the labels by ggrepel results in a messy look even though the points are quite regularly aligned vertically, so I've tried to arrange the labels in more orderly fashion on the right hand-side. The points labelled "2005-2009" and "2000-2004" overlap almost completely so I've set direction="both" in geom_text_repel to avoid overcrowding labels on the right hand-side. I've also set min.segment.length=Inf because drawing segments adds unnecessary clutter for only 5 data points. I wish to align "2005-2009" horizontally with its data point, but tweaking nudge_x, hjust and vjust values fails to do that.


Solution

  • For these data, geom_text_repel might be overkill. You can achieve similar spacing with geom_label. Only the "2005-2009" data point needs a different horizontal alignment from the rest, which can be be accomplished through the hjust parameter:

    ggplot(df, aes(x=I, y=G/100, label=interval)) + geom_point() + 
      geom_label(aes(label=interval, hjust = ifelse(interval == '2005-2009', 1, 0)), size=ts, label.padding = unit(0.3, "lines"), fill = NA, label.size = NA) +
      scale_x_continuous(limits=c(-1,1), breaks=c(-1,0,1), labels=c("", 0, ""), expand=c(0,0)) + 
      scale_y_continuous(limits=c(0,1), breaks=c(0,.5,1), expand=c(0,0)) + 
      geom_hline(yintercept=0.5) + geom_vline(xintercept=0) + 
      annotate("text", size=ts, x=0.02, y=0.03, label="0", hjust=0) + 
      annotate("text", size=ts, x=0.97, y=0.03, label="I", hjust=1, fontface="italic") + 
      annotate("text", size=ts, x=0.02, y=0.47, label="0.5", hjust=0) + 
      annotate("text", size=ts, x=0.02, y=0.97, label="G", hjust=0, fontface="italic") + pt
    

    enter image description here