- each of the subfigures is a ggplot (or other grid::grob object)
- each of the subfigures is given a label in the top-left hand corner
I've been using ggplot to draw graphs (the graph theory kind). It isn't quite set up to do this and indeed, igraph has perfectly good graph-drawing functions. They just don't return grobs (neither does sna::gplot), so they are a bit limited to my mind. Hence, I've set up a couple of simple functions to make ggplot figures out of igraph structures using the igraph::layout functions
Here's an example figure from my current dissertation. We first set up a theme to remove the weird default background for ggplot figures, import some libraries and define a function that returns a label in a rectangle.
library(igraph)
library(grid)
library(gridExtra)
library(ggplot2)
theme_nothing <- function(base_size = 12, base_family = "Helvetica"
){
# blank plotting background for ggplot2
# - for removing all axes etc when plotting igraphs in ggplot2
# - copied from ggplot2 docs
theme_bw(base_size = base_size, base_family = base_family
) %+replace% theme(
rect = element_blank(),
line = element_blank(),
text = element_blank(),
legend.position = 'none'
)
}
labelGrob <- function(label){
# function to generate subfigure labelsrequire(grid) require(gridExtra) textGrob(label, y = unit(0.9, 'npc')) }Then we define a function to convert an igraph object into a dataframe of x/y coords so that it can be plotted in ggplot.igraph_to_ggplot_df <- function( X, coord.func = layout.fruchterman.reingold ){ # Makes a dataframe of vertex / edge positions from an igraph # object stopifnot(is(X, "igraph"))# Give the vertices some names, if they don't already have them # then use the coord.func to define X/Y positions for each vertex v.names <- names(V(X)) if(is.null(v.names)){v.names <- paste0('v', 1:length(V(X)))} v.coords <- scale(coord.func(X)) dimnames(v.coords) <- list(v.names, c('x', 'y')) # Obtain the edges for the graph, give them names (if they don't # already have them) and then associate the start and end point # of each edge with the X/Y coords of the relevant vertex e.list <- get.edgelist(X) stopifnot(nrow(e.list) > 0) e.names <- names(E(X)) if(is.null(e.names)){ # assume the graph isn't null e.names <- paste0('e', 1:nrow(e.list)) } dimnames(e.list) <- list(e.names, c('head', 'tail')) stopifnot(length(intersect(v.names, e.names)) == 0) # convert the edge / vertex coords into a single dataframe for use in ggplot edge_df <- data.frame( element.name = rep(e.names, 2), element.label = '', element.type = 'edge', x = v.coords[as.vector(e.list[, c('head', 'tail')]), 'x'], y = v.coords[as.vector(e.list[, c('head', 'tail')]), 'y'] ) vertex_df <- data.frame( element.name = rownames(v.coords), element.label = rownames(v.coords), element.type = 'vertex', x = v.coords[, 'x'], y = v.coords[, 'y'] ) graph_df <- rbind(edge_df, vertex_df) graph_df }Then we define a function to turn igraph objects into ggplot graphical objects, so that these can be arranged and plotted out using grid.igraph_ggplot <- function( X, coord.func = layout.fruchterman.reingold, offset = 0.5 ){ # Converts a graph into x/y coords, # then plots the graph as straight-line joined coords d.frame <- igraph_to_ggplot_df(X, coord.func) # Note that if I don't change the offset myself, # arrangeGrob cuts off distal bits of my graph x.range <- range(d.frame$x) y.range <- range(d.frame$y) g <- ggplot(data = d.frame, aes(x = x, y = y, group = element.name, label = element.label) ) + geom_line( ) + geom_point(colour = 'darkgoldenrod1', size = 10 ) + geom_point(colour = 'white', size = 7 ) + geom_text(hjust = 0.5, vjust = 0.5, aes(colour = element.type) ) + theme_nothing( ) + xlim(x.range[1] - offset, x.range[2] + offset ) + ylim(y.range[1] - offset, y.range[2] + offset) g }This is just an example of a figure generated with the above code. We define the incidence matrix of a graph, and plot out the incidence, adjacency and Laplacian matrices of that graph (as tables) and a picture of the graph itself. The labels are added using arrangeGrob and labelGrob: I define an arrangement of 8 panels (2rows x 4 columns). The labels are placed into the first and third columns (which are 1/4 the width of the second and fourth columns.) fig1_2_1 <- function(){# Define a graph using it's incidence matrix, then plot # Signed incidence matrix D <- matrix(c(1, 1, 1, 0, -1,0, 0, 1, 0,-1, 0,-1, 0, 0,-1, 0, 0, 0, 0, 0 ), byrow = TRUE, nrow = 5, dimnames = list(paste0('v', 1:5), paste0('e', 1:4))) B <- abs(D) # Unsigned incidence matrix Q <- D %*% t(D) # Laplacian deg <- diag(Q) # Degree vector A <- diag(deg) - Q # Adjacency matrix X <- graph_from_adjacency_matrix( A, mode = 'undirected' ) g1 <- tableGrob(A) g2 <- tableGrob(D) g3 <- igraph_ggplot(X, offset = 0.25) g4 <- tableGrob(Q) ag <- arrangeGrob(labelGrob('a'), g1, labelGrob('b'), g2, labelGrob('c'), g3, labelGrob('d'), g4, nrow = 2, widths = c(1,4,1,4)) grid.draw(ag) NULL }------Voila!---------# TODO - show how to do the same thing with grid.draw(a); grid.draw(b) where 'a' contains figures and 'b' contains panel labels# TODO - eugh! work out why blogger is making my code/paragraphs so ugly?

No comments:
Post a Comment