Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I arrange the labels of the x-axis in an alluvial plot using ggrepel?

I am building an alluvial plot to show if categories of species Red Listining are present or not in legislations. Some groups like "Lampreys" and "Dragonflies and damselflies" are short compared to other groups and I wanted to move the labels out connecting them to the graph with an arrow each.

I have attempted using ggrepel, although my script and output returns below graph attempt of alluvial plot with messy label arrows The code I built to create it the following:

library(ggalluvial)
library(ggplot2)
library(ggrepel)

set.seed(123)
nome_lista_levels <- c("Bees and other hymenopterans", "Dragonflies and damsleflies", "Lampreys", "Other insects", "Mammals",
                       "Reptiles", "Amphibians", "Boned fish", "Sharks")
status_ita_levels <- c("Threatened", "Not Threatened", "Data Deficient")
policy_levels <- c("Policy Covered", "Not Covered")
alluvial_data_test <- expand.grid(
  Nome_lista = nome_lista_levels,
  Status_ITA = status_ita_levels,
  at.least.one.policy = policy_levels
)
alluvial_data_test$count <- sample(10:100, nrow(alluvial_data_test), replace = TRUE)
alluvial_data_test$Nome_lista <- factor(alluvial_data_test$Nome_lista, levels = nome_lista_levels)
alluvial_data_test$Status_ITA <- factor(alluvial_data_test$Status_ITA, levels = status_ita_levels)
alluvial_data_test$at.least.one.policy <- factor(alluvial_data_test$at.least.one.policy, levels = policy_levels)

non_plants <- ggplot(data = as.data.frame(alluvial_data_test),
                     aes(y = log(count + 1),
                         axis1 = Nome_lista,
                         axis2 = Status_ITA,
                         axis3 = at.least.one.policy)) +
  geom_alluvium(aes(fill = Nome_lista), curve_type = "sigmoid", alpha = 0.7, size = 0.6) +
  geom_stratum(aes(fill = Nome_lista), width = 0.2, fill = "white") +  
  geom_text_repel(
    data = alluvial_data_test,
    stat = "stratum",
    aes(label = after_stat(stratum)),
    nudge_x = 0.3,      # fixed numeric nudge to right
    size = 3,
    segment.size = 0.2,
    segment.color = "grey50",
    direction = "y"
  ) +
  geom_text(
    stat = "stratum",
    aes(label = after_stat(stratum)),
    size = 3,
    color = "black",
    data = subset(as.data.frame(alluvial_data_test), !(Nome_lista %in% label_data$Nome_lista))
  ) +
  scale_x_discrete(
    limits = c("Nome_lista", "Status_ITA", "at.least.one.policy"),
    expand = c(0.15, 0.05)) +
  theme_void() +
  theme(legend.position = "none") +
  theme(text = element_text(family = "Roboto")) + 
  scale_fill_brewer(palette = "Set3")

What I would like is that labels of only those species groups that are much smaller than the others sit outside of the columns, while the other labels are kept in the white blocks. Overall, I would like to tidy up the labels and make them suitable for publication.

like image 627
Ema_Plant95 Avatar asked Oct 30 '25 18:10

Ema_Plant95


2 Answers

The parameter nudge_x can be passed a vector instead of a fixed number. From my test it starts from bottom left and go from there.

Edit: following your comment, could you try with this version? I added 5 0s at the end so it won’t recycle the vector. Hopefully it is what it needs. :

nudge_x = c(0.3,0,0,0,0,0,0,0.3,0.3,0,0,0,0,0,0,0,0,0,0)
like image 83
joblolabinette Avatar answered Nov 01 '25 08:11

joblolabinette


You can use ggplot_build() to build the plot data table gg_pb, then gg_pb$data[[3]] will include the data used to plot the labels: label_table When ymax - ymin is larger than the text size the text-label will fit and we can remove the nudge_x of 0.3. So in this case only the label CITIES will be repelled.

library(ggalluvial)
library(ggplot2)
library(ggrepel)

alluvial_data <- data.frame(
  Nome_lista = rep(c("Red List", "CITES", "National List", "Regional List"), each = 8),
  Status_ITA = rep(c("Native", "Alien", "Naturalized", "Invasive"), times = 8),
  at.least.one.policy = rep(c("Yes", "No"), times = 16),
  count = c(rep(400,8), rep(.1,8), rep(800,8), rep(1800,8))
)                     # rep(2,8) to see that it works

non_plants <- ggplot(data = as.data.frame(alluvial_data),
                     aes(y = log(count + 1),
                         axis1 = Nome_lista,
                         axis2 = Status_ITA,
                         axis3 = at.least.one.policy)) +
  geom_alluvium(aes(fill = Nome_lista), curve_type = "sigmoid", alpha = 0.7, size = 0.6) +
  geom_stratum(aes(fill = Nome_lista), width = 0.2, fill = "white") +  
  geom_text_repel(
    stat = "stratum",
    aes(label = after_stat(stratum)),
    nudge_x = 0.3,      # fixed numeric nudge to right
    size = 3,
    segment.size = 0.2,
    segment.color = "grey50",
    direction = "y"
  ) +
  scale_x_discrete(
    limits = c("Nome_lista", "Status_ITA", "at.least.one.policy"),
    expand = c(0.15, 0.05)) +
  theme_void() +
  theme(legend.position = "none") +
  theme(text = element_text(family = "Roboto")) + 
  scale_fill_brewer(palette = "Set3")

gg_pb <- ggplot_build(non_plants)
gg_pb$data[[3]] <- within(gg_pb$data[[3]], x <- x - (ymax - ymin > size) * 0.3)
plot(ggplot_gtable(gg_pb))

giving res

like image 31
Tim G Avatar answered Nov 01 '25 08:11

Tim G