Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dodge overlapping segments to keep them parallel

Tags:

r

ggplot2

I have a data frame like this:

structure(list(x = c(65.11, 65.11, 65.11, 43.72, 43.72, 43.72, 
43.72, 43.72, 43.72, 43.72, 43.72, 59.89, 59.89, 59.89, 59.89, 
36.24, 36.24, 36.24, 36.24, 67.88, 37.89, 37.89, 37.89, 56.05, 
56.05, 56.05, 60.16, 60.16, 60.16, 30.92, 30.92, 30.92, 47.55, 
47.55, 47.55), y = c(32.17, 32.17, 32.17, 56.09, 56.09, 56.09, 
56.09, 56.09, 56.09, 56.09, 56.09, 15.64, 15.64, 15.64, 15.64, 
81.61, 81.61, 81.61, 81.61, 56.96, 21.69, 21.69, 21.69, 86.47, 
86.47, 86.47, 68.31, 68.31, 68.31, 51.56, 51.56, 51.56, 43.44, 
43.44, 43.44), xend = c(59.89, 60.16, 43.72, 59.89, 37.89, 56.05, 
60.16, 65.11, 36.24, 30.92, 47.55, 37.89, 65.11, 67.88, 30.92, 
37.89, 56.05, 30.92, 47.55, 60.16, 43.72, 59.89, 30.92, 43.72, 
36.24, 60.16, 43.72, 67.88, 47.55, 43.72, 36.24, 37.89, 59.89, 
37.89, 43.72), yend = c(15.64, 68.31, 56.09, 15.64, 21.69, 86.47, 
68.31, 32.17, 81.61, 51.56, 43.44, 21.69, 32.17, 56.96, 51.56, 
21.69, 86.47, 51.56, 43.44, 68.31, 56.09, 15.64, 51.56, 56.09, 
81.61, 68.31, 56.09, 56.96, 43.44, 56.09, 81.61, 21.69, 15.64, 
21.69, 56.09), node = c("484", "484", "484", "309", "309", "309", 
"309", "309", "309", "309", "309", "740", "740", "740", "740", 
"151", "151", "151", "151", "11", "26", "26", "26", "991", "991", 
"991", "731", "731", "731", "714", "714", "714", "99", "99", 
"99")), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, 
-35L))

I can plot the graph like below:

df %>% 
  ggplot() +
  geom_point(aes(x, y), shape = 21, size = 5, stroke = 1, colour = 'black', fill = 'blue') +
  geom_segment(aes(x = x, y = y, xend = xend, yend = yend),
               colour = 'red')

enter image description here

But this is in fact directed graph, so each segments has two directed components overlapping. I have tried to fix it with position_dodge2, but the following attempt is not as neat as needed:

df %>% 
  ggplot() +
  geom_point(aes(x, y), shape = 21, size = 5, stroke = 1, colour = 'black', fill = 'blue') +
  geom_segment(aes(x = x, y = y, xend = xend, yend = yend),
               colour = 'red',
               position = position_dodge2(width = 3))

enter image description here

I'd love to make the overlapping segments parallel, probably dodging their ends a little from the node position. How can I achieve this?

like image 422
Xaume Avatar asked Mar 04 '20 07:03

Xaume


1 Answers

If you don't mind a bit of data wrangling, you could use the 'ggraph' package to do ggplot2 graphs. The approach below assumes that a node can be unique identified by x or xend positions.

library(ggraph)
library(igraph)

nodes <- unique(df$x, df$xend)

elist <- cbind(df$node[match(df$x, nodes)],
               df$node[match(df$xend, nodes)])


gra <- graph_from_edgelist(elist)

lay <- create_layout(gra, "auto")
lay$x <- df$x[match(lay$name, df$node)]
lay$y <- df$y[match(lay$name, df$node)]


ggraph(lay) +
  geom_edge_parallel(end_cap = circle(.5), start_cap = circle(.5),
                     colour = "red",
                     arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  geom_node_point(shape = 21, size = 5, stroke = 1, colour = 'black', fill = 'blue')

enter image description here

EDIT: I found it easiest to manipulate edge aesthetics at the graph stage, while node aesthetics can be easily manipulated at the layout stage. Example:

gra <- graph_from_edgelist(elist)
E(gra)$size <- sample(1:10, length(E(gra)), replace = T)

lay <- create_layout(gra, "auto")
lay$x <- df$x[match(lay$name, df$node)]
lay$y <- df$y[match(lay$name, df$node)]
lay$size <- sample(1:10, nrow(lay), replace = T)


ggraph(lay) +
  geom_edge_parallel(end_cap = circle(.5), start_cap = circle(.5),
                     colour = "red", aes(edge_width = size),
                     arrow = arrow(length = unit(2, 'mm'), type = 'closed')) +
  geom_node_point(shape = 21, aes(size = size), stroke = 1, colour = 'black', fill = 'blue') 
like image 92
teunbrand Avatar answered Sep 28 '22 09:09

teunbrand