Search code examples
rggplot2geom-colgeom-segment

Slightly change geom_segment's position of x only, but keep position of xend constant


I want to draw a bar chart with three columns, where two arrows start from one column to the other two columns and these arrows do not overlap each other, as shown below.

enter image description here

I managed to produce the chart and I used geom_segments to draw the arrows. However, the arrows overlap when they start from the column control.

enter image description here

I thought I could handle the problem by setting geom_segment(..., position = position_nudge(x = 0.25)). However, this changes not only the position of x but also xend. Then, how should I slightly change geom_segment's position of x only, keeping the position of xend constant?

enter image description here

MWE

library(tidyverse)

demoData <- tribble(
  ~priming, ~rt,
  "control", 374,
  "phonological", 267,
  "orthographic", 304
) |>
mutate(
  diff.from.baseline = rt - first(rt)
)
  
baseline <- demoData |>
  filter(priming == "control") |>
  dplyr::select(rt) |>
  pull()

demoData |>
  ggplot(
    aes(
      x = priming,
      y = rt
    )
  ) +
  geom_col() +
  #### from control to orthographic
  geom_segment(
    aes(
      x = "control",
      y = baseline,
      xend = "control",
      yend = baseline + 50
    )#,
    #position = position_dodge2(
    #  width = 0.5,
    #  preserve = "total"
    #  )
    #position = position_nudge(
    #  x = 0.25,
    #  xend = 0
    #)
  ) +
  geom_segment(
    aes(
      x = "control"  + 0.25,
      y = baseline + 50,
      xend = "orthographic",
      yend = baseline + 50
    )#,
    #position = position_dodge2(
    #  width = 1,
    #  preserve = "total"
    #  )
    #position = position_nudge(
    #  x = 0.25,
    #  xend = 0
    #)
  ) +
  geom_segment(
    aes(
      x = "orthographic",
      y = baseline + 50,
      xend = "orthographic",
      yend = demoData |>
        filter(priming == "orthographic") |>
        dplyr::select(rt) |>
        pull()
    ),
    arrow = arrow()
  ) +
  #### from control to phonological
  geom_segment(
    aes(
      x = "control",
      y = baseline,
      xend = "control",
      yend = baseline + 100
    )
  ) +
  geom_segment(
    aes(
      x = "control",
      y = baseline + 100,
      xend = "phonological",
      yend = baseline + 100
    )
  ) +
  geom_segment(
    aes(
      x = "phonological",
      y = baseline + 100,
      xend = "phonological",
      yend = demoData |>
        filter(priming == "phonological") |>
        dplyr::select(rt) |>
        pull()
    ),
    arrow = arrow()
  )

Solution

  • Here's an approach:

    segments <- data.frame(seg = rep(c(1:2), each = 4),
                           x = c(0.8, 0.8, 3, 3, 1.2, 1.2, 2, 2),
                           y = c(as.numeric(demoData[1,2]), 450, 
                                            450, as.numeric(demoData[3,2]),
                                as.numeric(demoData[1,2]), 425, 
                                           425, as.numeric(demoData[2,2])))
    
    ggplot() +
      geom_path(data = segments, aes(x, y, group = seg), arrow = arrow()) +
      geom_col(data = demoData, 
               aes(x = as.numeric(factor(demoData$priming, 
                                         levels = demoData$priming)), rt)) +
      scale_x_continuous(breaks = 1:3, labels = demoData$priming)
    

    enter image description here