rggplot2gridextrar-gridgrob

arrangeGrob() and similar alternatives do not accept a list of grobs . At grid.draw, returns: Error in gList(...) : only 'grobs' allowed in "gList"


I am making a large volume of plots from survey data that requires the entire text of each question to be displayed next to the resulting bar chart. Arranging a grid of grobs seems the best way to do this. However, I have tried several packages, and have the same issue with the grid arrange step not recognizing the input list of grobs as grobs at the grid.draw() step.

Manually typing in the list references for each grob works fine, but this is not an acceptable solution as the volume of plots mandates an automated process.

# Dummy data set
question <- c("Q1. A very long text like this one is hard to use as a label.",
              "Q2. A very long text like this one is hard to use as a label.",
              "Q3. A very long text like this one is hard to use as a label.",
              "Q4. A very long text like this one is hard to use as a label.",
              "Q5. A very long text like this one is hard to use as a label.")

variable <- c("V1","V2")
set.seed(42)
response <- round(runif(n = length(question) * length(variable), min = 1, max = 10), 0)

# Create a data frame
df_example <- expand.grid(Question = question, Variable = variable)
df_example$Response <- rep(response, length.out = nrow(df_example))

grob_list <- list()
for (question in unique(df_example$Question)) {
  
  # Text grob
  # used textbox_grob() over alternatives for ease of wrapping text.
  text_grob <- textbox_grob(question, x = 0.05, y = 0.5, hjust = 0, vjust = 0.5, gp = gpar(col = "black"),
                            maxwidth = 1,
                            padding = unit(c(0, 20, 0, 0), "pt"))  
  # Bar chart
  bar_chart <- ggplot(df_example, aes(x = Variable, y = Response, fill = Variable)) +
    geom_bar(stat = "identity", position = "dodge") +
    coord_flip() +
    theme_minimal() +
    theme(legend.position = "none") +
    theme(axis.title.x = element_blank()) +
    theme(axis.title.y = element_blank())

  # Convert ggplot to grob
  bar_grob <- ggplotGrob(bar_chart)
  
  # Add text and bar chart grobs to the list of grobs
  grob_list[[length(grob_list) + 1]] <- list(text_grob, bar_grob)
}

### a. Required step for automation-
# Arrange grobs from list into a grid
combined_grobs <- arrangeGrob(grob = grob_list, ncol = 2)
# returns an error at grid.draw(): 
# > Error in gList(...) : only 'grobs' allowed in "gList"


### b. Manual step that works but not practical at scale-
# Arrange grobs manually using arrangeGrob from grid
combined_grobs <- arrangeGrob(grob_list[[1]][[1]], grob_list[[1]][[2]], 
                              grob_list[[2]][[1]], grob_list[[2]][[2]],
                              grob_list[[3]][[1]], grob_list[[3]][[2]],
                              grob_list[[4]][[1]], grob_list[[4]][[2]],
                              grob_list[[5]][[1]], grob_list[[5]][[2]],
                              ncol = 2
                              )

# Display after step a or b above. 
grid.newpage()
grid.draw(combined_grobs)


Solution

  • An easier approach would be to use facetting and ggtext::element_textbox(_simple) to automatically wrap the question text:

    library(ggplot2)
    library(ggtext)
    
    ggplot(df_example, aes(y = Variable, x = Response, fill = Variable)) +
      geom_bar(stat = "identity", position = "dodge") +
      facet_wrap(~Question, ncol = 1, strip.position = "left") +
      theme_minimal() +
      theme(
        legend.position = "none",
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        strip.text.y.left = ggtext::element_textbox_simple(
          hjust = 0,
          padding = unit(c(0, 20, 0, 0), "pt")
        ),
        strip.placement = "outside"
      )
    

    enter image description here

    UPDATE To make your code work requires two small fixes. First, your grob_list is a nested list, i.e. it is a list of lists of grobs instead of a list of grobs. To fix that use c() to build your grob_list in the for loop. Second, the name of the argument is grobs= not grob=.

    library(ggplot2)
    library(gridtext)
    library(grid)
    library(gridExtra)
    
    grob_list <- list()
    for (question in unique(df_example$Question)) {
      text_grob <- textbox_grob(question,
        x = 0.05, y = 0.5, hjust = 0, vjust = 0.5, gp = gpar(col = "black"),
        maxwidth = 1,
        padding = unit(c(0, 20, 0, 0), "pt")
      )
      bar_chart <- ggplot(df_example, aes(x = Variable, y = Response, fill = Variable)) +
        geom_bar(stat = "identity", position = "dodge") +
        coord_flip() +
        theme_minimal() +
        theme(legend.position = "none") +
        theme(axis.title.x = element_blank()) +
        theme(axis.title.y = element_blank())
      bar_grob <- ggplotGrob(bar_chart)
      grob_list <- c(grob_list, list(text_grob, bar_grob))
    }
    
    combined_grobs <- arrangeGrob(grobs = grob_list, ncol = 2)
    
    grid.newpage()
    grid.draw(combined_grobs)
    

    enter image description here