rggplot2facet-gridggrepelggh4x

How to repel texts equidistantly on a secondary y-axis of a facet_grid?


It is possible to insert variable names and their specific values ​​between the last column of facets and the strips of a facet_grid, using the scales_y function provided by Stefan.

However, these inserted texts can sometimes be too close together and overlap, making them unreadable.

Question: Is there a way to space them equidistantly along the secondary axis of each facet in the facet_grid, as shown below? Equidistance should therefore depend on the number of texts.

This might be possible using geom_text_repel(), but I'm struggling to use it in this particular context.

Initial plot:

enter image description here

Desired plot:

enter image description here

Data:

library(tidyverse)
library(stringr)
df <- mpg |> 
  filter(str_detect(manufacturer, "audi|chevrolet|dodge")) |> 
  mutate(limit_1=13.5, limit_2=14, limit_3=14.5)

# Create a list of y scales per drv
scales_y <- df |>
  distinct(drv, limit_1, limit_2, limit_3) |>
  split(~drv) |>
  lapply(\(x) {
    scale_y_continuous(
      sec.axis = dup_axis(
        breaks = c(x$limit_1, x$limit_2, x$limit_3),
        labels = paste0(c("lim1", "lim2", "lim3"), " (", c(x$limit_1, x$limit_2, x$limit_3), ")")
        )
      )
    })

# plot
ggplot(df, aes(displ, cty)) + 
  geom_point() + 
  facet_grid(drv ~ manufacturer, scales = "free") +
  geom_hline(aes(yintercept = limit_1, group = drv), linetype = "longdash", colour = "blue") +
  geom_hline(aes(yintercept = limit_2, group = drv), linetype = "longdash", colour = "green4") +
  geom_hline(aes(yintercept = limit_3, group = drv), linetype = "longdash", colour = "red") +
  ggh4x::facetted_pos_scales(y = scales_y) +
  theme(
    strip.placement = "outside",
    axis.title.y.right = element_blank(),
    axis.ticks.y.right = element_blank(),
    axis.text.y.right = element_text(size = 12, colour = c("blue","green4","red"))) +
  theme(
    strip.background = element_rect(color="black"),
    strip.text.x = element_text(size = 13, color = "black"),
    strip.text.y.right = element_text(size = 13, color = "black", angle = -90),
    axis.title.x = element_text(vjust = -0.5, size = 13),
    axis.title.y = element_text(vjust = 3, size = 13),
    axis.text = element_text(size = 10))

Solution

  • One way to achieve your desired result would be to keep the secondary axis labels, but make them transparent to make some room for the 'real' labels. Then, use geom_segment and geom_label to add the labels and connecting lines. Additionally we have to make some more room to account for the nudging of the labels. To this end I use transparent ticks and add some more space by increasing the tick length.

    library(tidyverse)
    
    df_hline <- df |>
      distinct(drv, manufacturer, limit_1, limit_2, limit_3) |>
      pivot_longer(
        -c(drv, manufacturer)
      ) |>
      mutate(
        color = case_match(
          name,
          "limit_1" ~ "blue",
          "limit_2" ~ "green",
          "limit_3" ~ "red"
        ),
        label = case_match(
          name,
          "limit_1" ~ "lim1",
          "limit_2" ~ "lim2",
          "limit_3" ~ "lim3"
        ),
        label = paste0(label, " (", value, ")")
      )
    
    df_label <- df_hline |>
      mutate(
        manufacturer = levels(factor(manufacturer))[n_distinct(manufacturer)]
      ) |> 
      distinct(
        drv, name,
        .keep_all = TRUE
      ) |>
      mutate(
        yend = scales::rescale(
          as.numeric(factor(name)), 
          from = c(0, n_distinct(name) + 1),
          to = c(0, 1)
        )
      )
    
    # plot
    ggplot(df, aes(displ, cty)) +
      geom_point() +
      facet_grid(drv ~ manufacturer, scales = "free") +
      geom_hline(
        data = df_hline,
        aes(yintercept = value, color = I(color)),
        linetype = "longdash"
      ) +
      geom_segment(
        data = df_label,
        aes(
          x = I(1), xend = I(1.15),
          y = value, yend = I(yend), 
          color = I(color)
        )
      ) +
      geom_label(
        data = df_label,
        aes(
          label = label, y = I(yend), color = I(color),
          x = I(1.15)
        ),
        hjust = 0,
        fill = NA,
        linewidth = 0,
        size = 12 / .pt
      ) +
      ggh4x::facetted_pos_scales(
        y = scales_y
      ) +
      theme(
        strip.placement = "outside",
        axis.title.y.right = element_blank(),
        axis.ticks.y.right = element_line(color = NA),
        axis.text.y.right = element_text(
          size = 12,
          # Make sec axis labels transparent
          colour = NA
        ),
        # Make some more room to account for the nudgeing of the labels
        axis.ticks.length.y.right = unit(20, "pt")
      ) +
      theme(
        strip.background = element_rect(color = "black"),
        strip.text.x = element_text(size = 13, color = "black"),
        strip.text.y.right = element_text(size = 13, color = "black", angle = -90),
        axis.title.x = element_text(vjust = -0.5, size = 13),
        axis.title.y = element_text(vjust = 3, size = 13),
        axis.text = element_text(size = 10)
      ) +
      coord_cartesian(clip = "off")
    

    enter image description here