2
library(tidyverse)
library(ggrepel)

df <- iris %>% 
  pivot_longer(starts_with("Sepal")) %>% 
  group_by(Species, name) %>% 
  summarise(
    y0 = min(value),
    y1 = max(value)
  ) 

I'm often using the ggrepel package for adding labels and annotating my ggplots, but sometimes the appearance can be a bit difficult to tweak. As an example for this question, I made the following stupid plot of the well known ìris data.

df %>% 
  ggplot(aes(x = name)) +
  geom_segment(
    aes(xend = name, y = y0, yend = y1, color = Species), size = 10
  ) +
  geom_text_repel(
    aes(label = Species, y = y1), direction = "y", nudge_x = .5
  )

I'm looking for a way to adjust the starting position of the ggrepel arrows such that they start at the right side of the segments, and not at the middle. One workaround would of course be to place the segments on top of the arrow:

df %>% 
  ggplot(aes(x = name)) +
  geom_text_repel(
    aes(label = Species, y = y1), direction = "y", nudge_x = .5
  ) +
  geom_segment(
    aes(xend = name, y = y0, yend = y1, color = Species), size = 10
  ) 

But that only works if you don't have any transparency, so i was wondering if there might be another solution. I guess I'm looking for a nudge_x_start or something like that.

EDIT: @stefan suggested using the point.padding parameter which works for the iris example, but unfortunately it doesn't work when the points are close to one another.

df2 <- enframe(month.name, "y0", "label") %>% 
  mutate(y1 = y0 + 1)

df2 %>% 
  ggplot(aes(x = "month")) +
  geom_segment(
    aes(xend = "month", y = y0, yend = y1, color = label), size = 10
  ) +
  geom_text_repel(
    aes(label = label, y = y0 + 0.5), direction = "y", nudge_x = 1/8,
    size = 5, point.padding = 1.25, hjust = 0
  ) +
  ylim(-12*2, 12*3)

Peter H.
  • 1,995
  • 8
  • 26

1 Answers1

2

Maybe this is what you are looking for. Even with a discrete axis ggplot2 uses numerics under the hood, i.e. first category is positioned at 1, second on 2, ... Therefore you could "nudge" the x inside aes as you did with the y. Do so you have to convert the categorical variable mapped on x to a numeric:

library(tidyverse)
library(ggrepel)

df <- iris %>%
  pivot_longer(starts_with("Sepal")) %>%
  group_by(Species, name) %>%
  summarise(
    y0 = min(value),
    y1 = max(value)
  )
#> `summarise()` has grouped output by 'Species'. You can override using the `.groups` argument.

df %>%
  ggplot(aes(x = name)) +
  geom_segment(
    aes(xend = name, y = y0, yend = y1, color = Species),
    size = 10
  ) +
  geom_text_repel(
    aes(label = Species, y = y1, x = as.numeric(factor(name)) + .06),
    direction = "y", nudge_x = .5
  )

df2 <- enframe(month.name, "y0", "label") %>% 
  mutate(y1 = y0 + 1)

df2 %>% 
  ggplot(aes(x = "month")) +
  geom_segment(
    aes(xend = "month", y = y0, yend = y1, color = label), size = 10
  ) +
  geom_text_repel(
    aes(label = label, y = y0 + 0.5, x = as.numeric(factor("month")) + .025), direction = "y", nudge_x = .5,
    size = 5, hjust = 0
  ) +
  ylim(-12*2, 12*3) +
  guides(color = "none")

stefan
  • 90,330
  • 6
  • 25
  • 51
  • It works for this example, but in my actual case the point.padding result in the arrows not being aligned on the x axis. – Peter H. Jun 30 '21 at 18:20
  • i edited my question to add an example of a plot to showcase the limitation of point.padding. – Peter H. Jul 01 '21 at 07:35
  • Hi Peter. Thanks for clarifying. I just made an edit. Hope I now got closer to what you meant by `nudge_start_x`. – stefan Jul 01 '21 at 07:54
  • ahh of course. Converting the factor to numeric and padding it manually solves the issues. Thanks! – Peter H. Jul 01 '21 at 11:14