3

I'm creating some DAGs using the ggdag package, which is a wrapper around ggplot and ggrepel. Because the text doesn't fit into the nodes I'm using geom_dag_label_repel, which is a wrapper around ggrepel's geom_label_repel.

Unfortunately the labels cover the arrowheads and this will not do. I need the labels to repel in a way that shows the arrowheads clearly. I've tried using the force_pull argument but this gives very capricious results. I've tried a bunch of other things as well: nudge_x/y doesn't seem to nudge the labels at all, dodging using position = 'dodge' also doesn't work.

The catch is that I'm generating the graphs using a custom function and so I would like any repelling to be automated within the function, so that when I supply coordinates and etc, the labels come out at the right place without me having to specify exactly where I want them for each individual graph. Here's a reprex:

library(latex2exp)
library(tidyverse)
library(dagitty)
library(ggdag)
library(repr)

hj_ggdag <- function(x,
                     y,
                     names,
                     arcs = cbind(0,0),
                     title = "",
                     contraction = .1, #unused
                     add_functions = 0, #unused
                     add_functions_text = NULL, #unused
                     text_shift = .2*add_points, #unused
                     padding = 0, # padding around box if labels = T
                     length = 0.2, #unused
                     cex = 1, #unused
                     adj = .5, #unused
                     box = TRUE,   #unused
                     model = NULL, #accepts causal model objects and returns ggdag
                     labels = FALSE,
                     textcol = 'white', # text colour (not label)
                     textsize = 3.88, #text size (not label)
                     force = 0, #repelling force between labels
                     ...) { # other arguments passed to ggdag and geom_dag_label_repel, e.g. force_pull, node = T/F
  
  # Step 1: First make the df
  if (!is.null(model)) {
    df <- tidy_dagitty(paste0('dag{',model$statement,'}'))
    df$data <- df$data %>%
      mutate(label = name)
  }  else {
    
    # Operates on other arguments
    # Placeholders
    nodes     <- LETTERS[1:length(names)]
    
    # Coordinates
    names(x) <- nodes  
    names(y) <- nodes  
    
    coords_df <- list(x = x , y = y) %>% coords2df
    
    # Statement  
    statement <-  paste(nodes[arcs[,1]], " -> ", nodes[arcs[,2]], collapse = "; ")
    
    # DAG df  
    df <-  paste("dag{", statement, "}") %>% dagitty
    coordinates(df) <- coords2list(coords_df)
    # imputing labels as an additional column so geom_dag_label_repel can accept it within the same aes
    df <- df %>% tidy_dagitty
    df$data <- df$data %>%
      mutate(label = names %>% as.character %>% .[match(df$data$name,LETTERS)])
  }
  # Step 2: Format and export
  p <- df %>% 
    ggdag(text = FALSE,...) + theme_dag() +
    labs(title = TeX(repr_text(title) %>% str_remove_all('\\"')))
  if (labels==TRUE){
    parse <- ifelse(class(names)=='expression',TRUE,FALSE)
    p + 
      geom_dag_label_repel(aes_string(label = 'label'),
                           show.legend = FALSE,
                           parse = parse,
                           box.padding = padding,
                           hjust = 0,
                           segment.color = 'red',
                           segment.size = 1,
                           min.segment.length=0,
                           ...)
  } else {
    p + 
      geom_dag_text_repel(aes_string(label = 'label'),
                          show.legend = FALSE,
                          parse = TRUE,
                          color=textcol,
                          size=textsize,
                          box.padding = 0,
                          force = 0
      )
  }
}

hj_ggdag(x = c(1,1,2),
       y = c(2,0,1),
       names = c(
         expression(paste(D[t-1]: "Democracy, last period")),
         expression(paste(Y[t]: "GDP per capita, this period")),  
         expression(paste(D[t]: "Democracy, this period"))
         ),
       arcs = cbind( c(1,2),
                     c(3,3)),
       title = "(c) Democratization (Przeworski and Limongi, 1997)",
       add_functions = 0, 
       contraction = .16, 
       padding = 0,
       labels = T,
       force_pull=0
)

Label still covers arrows

Is there any way I can dodge the labels from the arrow heads? Thanks!

jonnyf
  • 198
  • 8
  • I am struggling with the same problem. Even when running code from the ggdag package author (https://malco.io/2019/09/17/tidy-causal-dags-with-ggdag-0-2-0/), I don't get his nice result. Actually, all my labels are placed on the nodes and edges. – JuergenL Jun 23 '23 at 09:24

1 Answers1

1

I was struggling to get the arrowheads visible as well. Both repel and label positioning seem pretty random to me. The workaround I've been using revolves around using ggplot2 functions over the ggdag wrapper function. That way, you can specify the ordering of the layers in your plot.

An example of my code:

ggplot(basic_dag, aes(x = x, y = y, xend = xend, yend = yend)) +
geom_dag_point(aes(color = status)) +
geom_dag_label_repel(aes(label = 'label', fill = status),
color = "white", fontface = "bold") +
geom_dag_edges()

The key here is to add geom_dag_edges() last so the edges and arrowheads are plotted on top of any labels. It's not a perfect solution but at least you can see the direction of the associations.