Search code examples
rggplot2overlaysubplotggraph

Overlaying subplot ggraph


I need to overlay a set of subplots over a ggraph. Every plot correspond to an especific node so they must be placed using the coordinates of every node. The following code creates the graph and the set of subplots to overlay.

 # Create the graph
 library(data.table)
 library(igraph)
 library(ggraph)

 mydata <- data.table(from=c("John", "John", "Jim"), to=c("John", "Jim", "Jack"))
 graph <- graph_from_data_frame(mydata)
 V(graph)$class <- c("John", "Jim", "Jack")

 ggraph(graph, layout = 'linear') + 
 geom_edge_link() + 
 geom_node_point() +
 geom_node_text(aes(label = class))

 # Plots to overlay
 John <- ggplot(diamonds, aes(carat)) + geom_histogram(fill = "red") + 
 ggtitle("John")
 Jim <- ggplot(diamonds, aes(depth)) + geom_histogram(fill = "blue") + 
 ggtitle("Jim")
 Jack <- ggplot(diamonds, aes(price)) + geom_histogram(fill = "green") + 
 ggtitle("Jack")

The following image ilustrates the actual and desired result. enter image description here


Solution

  • You can use ggraph::create_layout to extract x and y coordinates of the nodes, and then use purrr::pmap to apply ggplot2::annotation_custom to each of your subplots.

    library(purrr)
    library(ggplot2)
    library(igraph)
    library(ggraph)
    
    # Your code, mostly unchanged (removed data.table)
    mydata <- data.frame(from=c("John", "John", "Jim"), to=c("John", "Jim", "Jack"))
    graph <- graph_from_data_frame(mydata)
    V(graph)$class <- c("John", "Jim", "Jack")
    
    
    John <- ggplot(diamonds, aes(carat)) + geom_histogram(fill = "red") + 
      ggtitle("John")
    Jim <- ggplot(diamonds, aes(depth)) + geom_histogram(fill = "blue") + 
      ggtitle("Jim")
    Jack <- ggplot(diamonds, aes(price)) + geom_histogram(fill = "green") + 
      ggtitle("Jack")
    
    # New code
    graph_df <- create_layout(graph, layout = 'linear')
    

    graph_df is a dataframe with contents:

      x y name class ggraph.orig_index circular ggraph.index
    1 1 0 John  John                 1    FALSE            1
    2 2 0  Jim   Jim                 2    FALSE            2
    3 3 0 Jack  Jack                 3    FALSE            3
    

    You can call ggraph(graph_df) directly; under the hood ggraph(graph) was doing the same steps.


    Now we make a nested list, where the first element is a list of the ggplot objects we want to use for insets (making sure they're in the right order, relative to their order in graph_df). The second element is a list of the x coordinates, and the third element is a list of the y coordinates. Then we apply a function that constructs grobs to inset, using the x and y coordinates to define a box where it will go in the final plot.

    grobs <- pmap(.l = list(plots = list(John, Jim, Jack), 
                            x = as.list(graph_df$x), 
                            y = as.list(graph_df$y)),
    
                  .f = function(plots, x, y) {
    
        annotation_custom(grob = ggplotGrob(plots + theme_grey(base_size = 4)),
                          xmin = x - .25, xmax = x + .25,
                          ymin = y + .1,  ymax = y + .6)
      })
    

    Then your code from above just needs to have this object added on and some fiddling with the limits:

    ggraph(graph_df) + 
      geom_edge_link() + 
      geom_node_point() +
      geom_node_text(aes(label = class)) + 
      expand_limits(x = c(0.5,3.5)) +
      coord_equal() +
      grobs
    

    enter image description here

    Some notes:

    • The construction of the inset plots can also likely be done programmatically using purrr functions.
    • The base_size = argument in the function we created will need to be fiddled with to your liking.
    • The offsets for x and y in the function also have to be done manually, based on the range of coordinates in your actual plot.