4

Updated question to incorporate a partial solution already answered on SO

I am using ggplot2 to create several plots and gridExtra to combine the plots into one figure with several panels, all in one column. My problem is that I can't get the space between the dot plot rows to be consistent in both plots.

enter image description here

library(ggplot2)
# data
  dat1 <- data.frame(VARIABLES=c("Item 1", "Item 2 is a little longer"),
                     est=c(.3, .5),
                     min=c(.2, .4),
                     max=c(.4, .7))
  dat2 <- data.frame(VARIABLES=c("Item 3", 
                                 "Item 4 is even longer if you can believe it",
                                 "And there is a third item",
                                 "And a fourth item"),
                     est=c(.3, .5, .3, .5),
                     min=c(.2, .4, .2, .4),
                     max=c(.4, .7, .4, .7))
  dat <- c("dat1", "dat2")
  labs <- c("Plot 1", "Plot2")
# create plots
  count <- 1
  for (i in dat) {
    p <- ggplot(get(i), aes(x=reorder(as.character(VARIABLES), est), 
                              y=est)) +
    geom_pointrange(aes(ymin=min,
                        ymax=max),
                    linetype="dashed") +
    geom_point(size=3) +
    ylim(-1,1) +
    theme_bw() +
    labs(title = labs[count]) +
    theme(legend.position="none") +
    coord_flip()
    assign(paste(i, "plot", sep="."), p)
    count <- count+1
  }
# combine plots
  library(gridExtra)
  # approach suggested by @baptise
  # http://stackoverflow.com/questions/13294952/left-align-two-graph-edges-ggplot
  gA <- ggplotGrob(dat1.plot)
  gB <- ggplotGrob(dat2.plot)
  maxWidth = grid::unit.pmax(gA$widths[2:5], gB$widths[2:5])
  gA$widths[2:5] <- as.list(maxWidth)
  gB$widths[2:5] <- as.list(maxWidth)
  grid.arrange(gA, gB, ncol=1)
Eric Green
  • 7,385
  • 11
  • 56
  • 102
  • I was going to suggest using `facets` instead of `grid.arrange`, but "ggplot2 does not currently support free scales with a non-cartesian coord or coord_flip." – shadow May 26 '14 at 05:57
  • @shadow, yes, I was disappointed to learn this. I originally thought facets might be the answer. – Eric Green May 26 '14 at 15:39
  • I think #1 is mostly a variation of [another question](http://stackoverflow.com/questions/20638294/geom-tile-and-facet-grid-facet-wrap-for-same-height-of-tiles/20639481#20639481) – baptiste May 26 '14 at 16:07
  • @baptiste, what is appropriate in this case since (1) has been answered in another question, but not (2)? Should I edit the question to incorporate the other answer for (1) and clarify that my question deals with (2)? – Eric Green May 26 '14 at 16:16
  • I have no idea. My guess is that it's best to have only one specific problem per question in the first place. Go ahead an edit, I've removed my closing vote. – baptiste May 26 '14 at 16:36
  • I got the 'answer' in the question above to work for me when I used `gA$widths[2:5] <- maxWidth` instead of `gA$widths[2:5] <- as.list(maxWidth)`, etc. – André C. Andersen Aug 27 '16 at 13:51

3 Answers3

9
library(gridExtra)
library(grid)

gb1 <- ggplot_build(dat1.plot)
gb2 <- ggplot_build(dat2.plot)

# work out how many y breaks for each plot
n1 <- length(gb1$layout$panel_params[[1]]$y.labels)
n2 <- length(gb2$layout$panel_params[[1]]$y.labels)

gA <- ggplot_gtable(gb1)
gB <- ggplot_gtable(gb2)

g <- rbind(gA, gB)

# locate the panels in the gtable layout
panels <- g$layout$t[grepl("panel", g$layout$name)]
# assign new (relative) heights to the panels, based on the number of breaks
g$heights[panels] <- unit(c(n1,n2),"null")

grid.newpage()
grid.draw(g)

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • this looks like it will work. thanks. however, i get to `g <- gtable:::rbind_gtable(gA, gB, gC, gD, gE, "last")` in my actual code (note i'm adding 3 more plots) and i get an error about unused arguments. i looked at the `rbind.gtable` documentation, but nothing jumped out at me. any guesses as to what is causing the error? – Eric Green May 26 '14 at 20:08
  • also, @baptiste, is it possible to modify the code to specify a specific space between rows? your code seems to answer the question perfectly. just wondering if it is possible to specify a particular spacing that could be applied to all plots. – Eric Green May 26 '14 at 20:13
  • I've updated [my original answer](http://stackoverflow.com/a/13295880/471093) to cater for multiple plots. Space between plots is defined by the plot margins; alternatively you could look at `?gtable_add_rows` and similar functions in the gtable docs. – baptiste May 26 '14 at 20:54
  • I accepted this answer because it gets the job done. Thanks! Your other code works great for multiple plots, though I was unsure how to modify to include the height adjustment you include in the current answer. A note for other folks coming to this thread: long labels get cutoff. – Eric Green May 27 '14 at 07:14
  • 1
    I was able to get the code in the answer working perfectly for multiple plots by adding parameters for my additional 3 plots: `gb3...gb5, n3...n5, gC...gE` and `g1 <- gtable:::rbind_gtable(gA, gB, "last")`, `g2 <- gtable:::rbind_gtable(g1, gC, "last")`,...`g <- gtable:::rbind_gtable(g3, gE, "last")`. I also had to update with `unit(n3,"null")...unit(n5,"null")`. Finally setting the output size with `cairo_pdf(f, width=7, height=9) and wrapping this around ` grid.draw(g)` with `dev.off()` produced a great looking figure. Thanks, @baptiste. – Eric Green May 27 '14 at 07:31
  • @EricGreen Do you have the code for multiple plots? – BioMan Jul 17 '15 at 10:58
  • @baptiste any chance you could update this for the latest gridExtra? having trouble getting this to work based on https://github.com/baptiste/gridextra/wiki/arranging-ggplot – Dominik Mar 03 '16 at 18:03
  • 1
    I don't know, but I think today the following syntax has changed: n1 <- length(gb1$layout$panel_params[[1]]$y.labels) to n1 <- length(gb1$layout$panel_ranges[[1]]$y.labels) – JulioSergio Mar 27 '18 at 23:40
  • Instead of using g <- rbind(gA, gB), it should be used g <- gtable:::rbind_gtable(gA, gB, "last") – JulioSergio Mar 27 '18 at 23:49
1

Here's one approach: fill in extra spaces for shorter labels and use a monospace font.

longest_name <- max(nchar(as.character(dat1$VARIABLES)), nchar(as.character(dat2$VARIABLES)))
fill_in_spaces <- function(v) paste0(paste0(rep(" ", longest_name - nchar(v)), collapse=""), v)
levels(dat1$VARIABLES) <- sapply(levels(dat1$VARIABLES), fill_in_spaces)
levels(dat2$VARIABLES) <- sapply(levels(dat2$VARIABLES), fill_in_spaces)

Then the plotting procedure is almost the same, just add

p <- p + theme(text=element_text(family="Courier", size=14))

enter image description here

There is a minor issue: levels are reordered, so Item 3 is now the last, but that can be easily fixed like described e.g. here.

Community
  • 1
  • 1
tonytonov
  • 25,060
  • 16
  • 82
  • 98
1
dat1$Plot <- "Plot 1"
dat2$Plot <- "Plot 2"
dataset <- rbind(dat1, dat2)

ggplot(
  dataset, 
  aes(
    y = reorder(as.character(VARIABLES), est), 
    x = est,
    xmin = min,
    xmax = max
  )) +
  geom_errorbarh() + geom_point() + 
facet_wrap(~Plot, ncol = 1, scales = "free_y")

enter image description here

Thierry
  • 18,049
  • 5
  • 48
  • 66