4

I'm trying to plot a network with ggraph and I'd like to add a circle around the graph, with the edges and nodes lying centered inside the circle.

Drawing the circle works just fine with the following code (adapted from Draw a circle with ggplot2)

gg_circle <- function(r, xc, yc, color = "black", fill = NA, lty = NA, size = NA, ...) {
  x <- xc + r*cos(seq(0, pi, length.out = 100))
  ymax <- yc + r*sin(seq(0, pi, length.out = 100))
  ymin <- yc + r*sin(seq(0, -pi, length.out = 100))
  annotate("ribbon", x = x, ymin = ymin, ymax = ymax, 
           color = color, fill = fill, lty = lty, size = size, ...)
} 

But I can't manage to match the position of the network layer(s) with the position of the circle, which results in both nodes and edges lying partially outside the circle:

see here for a screenshot

That's the crucial part of the code as it is right now (using highschool from ggraph as an example dataset for reproducibility purposes):

library(ggraph)
library(igraph)
graph <- graph_from_data_frame(highschool)

ggraph(graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), 
                 check_overlap = TRUE, repel = TRUE, 
                 nudge_x = 0.1, nudge_y = 0.1) +
  gg_circle(r = 11, xc = 0, yc = 0, lty = 1, size = 0.2) +
  theme(axis.ticks.length = unit(0, "cm"),
        legend.position = "none",
        plot.margin = unit(c(0, 0, 0, 0), "cm"),  
        panel.spacing = unit(c(0, 0, 0, 0), "cm")) +
  coord_fixed()

Any ideas or suggestions on how to fix this? Thanks in advance!

Z.Lin
  • 28,055
  • 6
  • 54
  • 94
lisah
  • 186
  • 1
  • 9

1 Answers1

7

I would try to use the node positions to find acceptable values for r, xc, and yc.

Step 1. Create plot (without the circle):

set.seed(9) # for reproducibility

p <- ggraph(graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name),
                 check_overlap = TRUE,
                 repel = TRUE,
                 nudge_x = 0.1,
                 nudge_y = 0.1) +
  theme(axis.ticks.length = unit(0, "cm"),
        legend.position = "none",
        plot.margin = unit(c(0, 0, 0, 0), "cm"),  
        panel.spacing = unit(c(0, 0, 0, 0), "cm")) +
  coord_fixed()

Step 2. Get data from the plot's geom_node_point() layer (the 2nd layer in this case). Modify the gg_circle code to take this dataframe as input, & calculate the appropriate circle centre coordinates / radius:

p.positions <- layer_data(p, i = 2L)

gg_circle_from_position <- function(data, 
                                    color = "black", fill = NA, 
                                    lty = NA, size = NA, ...){

  coord.x <- data[, 'x']
  coord.y <- data[, 'y']

  xc = mean(range(coord.x))
  yc = mean(range(coord.y))
  r = max(sqrt((coord.x - xc)^2 + (coord.y - yc)^2)) * 1.05
  # expand radius by 5% so that no node sits exactly on the line;
  # increase from 1.05 to some larger number if more buffer is desired.

  # no change to this part
  x <- xc + r*cos(seq(0, pi, length.out = 100))
  ymax <- yc + r*sin(seq(0, pi, length.out = 100))
  ymin <- yc + r*sin(seq(0, -pi, length.out = 100))
  annotate("ribbon", x = x, ymin = ymin, ymax = ymax, 
           color = color, fill = fill, lty = lty, size = size, ...)

}

Step 3. Add circle to plot:

p + gg_circle_from_position(data = p.positions, lty = 1, size = 0.2)

plot

Z.Lin
  • 28,055
  • 6
  • 54
  • 94