5

I am creating a network visualisation of a single "center" node surrounded by about 600 nodes, using igraph in R.

The nodes overlapped, which I could then solve using the answer to this question (using qgraph).

However, this solution only seems to work with nodes that are all the same size. In my network, the node size is variable. Is there a way to avoid overlap by accounting for the node sizes in determining the distances between them?

Example code below:

# create network
net <- graph_from_data_frame(d=links, vertices=nodes, directed=T)

# set colors
colrs <- c("#8DD3C7", "#FFFFB3")
V(net)$color <- colrs[V(net)$type]
# no labels
V(net)$label <- NA

# create a network graph with non-overlapping nodes:
# using https://stackoverflow.com/questions/39290909/igraph-resolving-tight-overlapping-nodes
e <- get.edgelist(net,names = F) 
l <- qgraph.layout.fruchtermanreingold(e,vcount=vcount(net))
plot(net,layout=l,vertex.size=4,edge.arrow.mode=0,vertex.label=NA)

This is the result:

non overlapping network - equal sized nodes

But now when I change the node sizes:

# setting node size based on data
V(net)$size <- V(net)$nodesize; 
# plot result 
plot(net,layout=l,edge.arrow.mode=0,vertex.label=NA)

...the nodes overlap:

overlapping nodes - varying size nodes

Thanks for any help here!

* edited - added example dataset: first 50 nodes *

Nodes data:

dput(head(nodes,50))

structure(list(id = c("s01", "s02", "s03", "s04", "s05", "s06", "s07", "s08", 
"s09", "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19", 
"s20", "s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29", "s30", 
"s31", "s32", "s33", "s34", "s35", "s36", "s37", "s38", "s39", "s40", "s41", 
"s42", "s43", "s44", "s45", "s46", "s47", "s48", "s49", "s50"), nodesize = 
c(50, 2.025, 2.025, 3.5, 1, 0.725, 2.875, 1.6, 0.175, 2.175, 0, 0.675, 0.5, 
15.7, 1.4, 0.4, 1.375, 0.425, 0.55, 7, 10.375, 1.125, 0.325, 0.925, 3.6, 0.525, 
0.9, 0.1, 0.5, 2.3, 1.825, 1.95, 0.325, 0.9, 3, 0.475, 0.1, 2.975, 6.1, 9.225,
 0.65, 3.05, 2.925, 6.35, 0.7, 0.2, 0.6, 1.7, 1.675, 1.425), type = c(1L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L,
 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L)), row.names = c(NA, 50L), class = 
"data.frame")

Links data:

dput(head(links,49))

structure(list(from = c("s01", "s01", "s01", "s01", "s01", "s01",
"s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", 
"s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", 
"s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", 
"s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", "s01", 
"s01", "s01", "s01", "s01", "s01", "s01", "s01"), to = c("s02", 
"s03", "s04", "s05", "s06", "s07", "s08", "s09", "s10", "s11", 
"s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19", "s20", 
"s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29", 
"s30", "s31", "s32", "s33", "s34", "s35", "s36", "s37", "s38", 
"s39", "s40", "s41", "s42", "s43", "s44", "s45", "s46", "s47", 
"s48", "s49", "s50")), row.names = c(NA, 49L), class = "data.frame")
Sam
  • 63
  • 1
  • 6

2 Answers2

2

I don't think you're likely to find another layout algorithm given that your graph is a 'star' graph. Most will push the most connected node to the centre.

There is a new package, graphlayouts that has the layout_with_stress that produces about the same result. One approach that might work is adjusting the scale of the node size. An easy way to do this is to adjust the scale of the sizes of the nodes so that they don't overlap. The ggraph library helps with that.

library(tidyverse)
library(igraph)
library(ggraph)
library(tidygraph)
library(graphlayouts)
library(scales)


g <- make_star(600) 

par(mar = rep(0,4))

V(g)$size <- sample(1:10, vcount(g), T)

plot(g, 
     vertex.label = NA, 
     layout = l)

Base Plot

(no_scale_g <- g %>% 
  as_tbl_graph() %>% 
  activate(nodes) %>% 
  mutate(size = sample(1:10, vcount(g), replace = T)) %>% 
  ggraph(., layout = 'stress')+
  geom_edge_fan(aes(alpha = ..index..), 
                show.legend = F, 
                check_overlap = T)+
  geom_node_point(aes(size = size, 
                      color = size))+
  coord_equal())

ggraph no scale

(scale_g <- no_scale_g+
  scale_size(range = c(1, 3)))

ggraph with node scaling

You can tweak the scaling parameters to ensure that there is no overlap. It's not perfect, but for static graphs this gets you pretty close.

Community
  • 1
  • 1
elliot
  • 1,844
  • 16
  • 45
1

This seems related to the following post, which mentions qgraph package's function for layouting, which has an argument for repulse.rad. To use it you first need to transform the igraph-network object g to an edgelist via

## Transform to edgelist with names = FALSE, otherwise it crashed for me
e <- get.edgelist(g, names = FALSE)

layout <- qgraph.layout.fruchtermanreingold(e, vcount = vcount(g),
  area = 6*(vcount(g)^2), repulse.rad = vcount(g)^3)

## Plot via igraph
plot.igraph(network, vertex.label = NA, layout = fixedLayout)

## Or plot via ggraph
ggraph(network, layout = fixedLayout) +
    geom_edge_link(color = "lightgrey", width = 0.3) +
    geom_node_point(size = 3) +
    theme_void()

You can tweak the area and repulse.rad parameters to your satisfaction.

I think modifying the layout should be the best way to really guarantee that there is no overlap, while I must admit the "fireworks"-style with some overlap is also quite nice!

Simon Stolz
  • 207
  • 2
  • 7