Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make consistent-width plots in ggplot (with legends)?

Tags:

r

ggplot2

I've got a few different categories that I want to plot. These are different categories, each with their own set of labels, but which makes sense to group together in the document. The following gives some simple stacked bar chart examples:

df <- data.frame(x=c("a", "b", "c"),                  y=c("happy", "sad", "ambivalent about life")) ggplot(df, aes(x=factor(0), fill=x)) + geom_bar() ggplot(df, aes(x=factor(0), fill=y)) + geom_bar() 

The problem is that with different labels, the legends have different widths, which means the plots have different widths, leading to things looking a bit goofy if I make a table or \subfigure elements. How can I fix this?

Is there a way to explicitly set the width (absolute or relative) of either the plot or the legend?

Chart 1 based on x (wider) Chart 2 based on y (narrower)

like image 614
jamie.f.olson Avatar asked Apr 27 '13 18:04

jamie.f.olson


People also ask

How do I add a legend to Ggplot?

You can place the legend literally anywhere. To put it around the chart, use the legend. position option and specify top , right , bottom , or left . To put it inside the plot area, specify a vector of length 2, both values going between 0 and 1 and giving the x and y coordinates.


1 Answers

Edit: Very easy with egg package

# install.packages("egg")  library(egg)  p1 <- ggplot(data.frame(x=c("a","b","c"),                         y=c("happy","sad","ambivalent about life")),              aes(x=factor(0),fill=x)) +        geom_bar() p2 <- ggplot(data.frame(x=c("a","b","c"),                         y=c("happy","sad","ambivalent about life")),              aes(x=factor(0),fill=y)) +        geom_bar()  ggarrange(p1,p2, ncol = 1) 

Original Udated to ggplot2 2.2.1

Here's a solution that uses functions from the gtable package, and focuses on the widths of the legend boxes. (A more general solution can be found here.)

library(ggplot2)    library(gtable)     library(grid) library(gridExtra)   # Your plots p1 <- ggplot(data.frame(x=c("a","b","c"),y=c("happy","sad","ambivalent about life")),aes(x=factor(0),fill=x)) + geom_bar() p2 <- ggplot(data.frame(x=c("a","b","c"),y=c("happy","sad","ambivalent about life")),aes(x=factor(0),fill=y)) + geom_bar()  # Get the gtables gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # Set the widths gA$widths <- gB$widths  # Arrange the two charts. # The legend boxes are centered grid.newpage() grid.arrange(gA, gB, nrow = 2) 

If in addition, the legend boxes need to be left justified, and borrowing some code from here written by @Julius

p1 <- ggplot(data.frame(x=c("a","b","c"),y=c("happy","sad","ambivalent about life")),aes(x=factor(0),fill=x)) + geom_bar() p2 <- ggplot(data.frame(x=c("a","b","c"),y=c("happy","sad","ambivalent about life")),aes(x=factor(0),fill=y)) + geom_bar()  # Get the widths gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # The parts that differs in width leg1 <- convertX(sum(with(gA$grobs[[15]], grobs[[1]]$widths)), "mm") leg2 <- convertX(sum(with(gB$grobs[[15]], grobs[[1]]$widths)), "mm")  # Set the widths gA$widths <- gB$widths  # Add an empty column of "abs(diff(widths)) mm" width on the right of  # legend box for gA (the smaller legend box) gA$grobs[[15]] <- gtable_add_cols(gA$grobs[[15]], unit(abs(diff(c(leg1, leg2))), "mm"))  # Arrange the two charts grid.newpage() grid.arrange(gA, gB, nrow = 2) 

enter image description here

Alternative solutions There are rbind and cbind functions in the gtable package for combining grobs into one grob. For the charts here, the widths should be set using size = "max", but the CRAN version of gtable throws an error.

One option: It should be obvious that the legend in the second plot is wider. Therefore, use the size = "last" option.

# Get the grobs gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # Combine the plots g = rbind(gA, gB, size = "last")  # Draw it grid.newpage() grid.draw(g) 

Left-aligned legends:

# Get the grobs gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # The parts that differs in width leg1 <- convertX(sum(with(gA$grobs[[15]], grobs[[1]]$widths)), "mm") leg2 <- convertX(sum(with(gB$grobs[[15]], grobs[[1]]$widths)), "mm")  # Add an empty column of "abs(diff(widths)) mm" width on the right of  # legend box for gA (the smaller legend box) gA$grobs[[15]] <- gtable_add_cols(gA$grobs[[15]], unit(abs(diff(c(leg1, leg2))), "mm"))  # Combine the plots g = rbind(gA, gB, size = "last")  # Draw it grid.newpage() grid.draw(g) 

A second option is to use rbind from Baptiste's gridExtra package

# Get the grobs gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # Combine the plots g = gridExtra::rbind.gtable(gA, gB, size = "max")  # Draw it grid.newpage() grid.draw(g) 

Left-aligned legends:

# Get the grobs gA <- ggplotGrob(p1) gB <- ggplotGrob(p2)  # The parts that differs in width leg1 <- convertX(sum(with(gA$grobs[[15]], grobs[[1]]$widths)), "mm") leg2 <- convertX(sum(with(gB$grobs[[15]], grobs[[1]]$widths)), "mm")  # Add an empty column of "abs(diff(widths)) mm" width on the right of  # legend box for gA (the smaller legend box) gA$grobs[[15]] <- gtable_add_cols(gA$grobs[[15]], unit(abs(diff(c(leg1, leg2))), "mm"))  # Combine the plots g = gridExtra::rbind.gtable(gA, gB, size = "max")  # Draw it grid.newpage() grid.draw(g) 
like image 129
Sandy Muspratt Avatar answered Oct 05 '22 03:10

Sandy Muspratt