Search code examples
rggplot2cowplotgrobpatchwork

Properly size multiple ggExtraPlot objects after calling ggMarginal in R


I want to create a facet-like effect with ggExtra::ggMarginal plots. This is not yet directly possible, so I tried to use patchwork::wrap_plots, cowplot::plot_grid, and egg::ggarrange to do it myself. I have everything properly developed—except for the plot sizing. patchwork and egg make the panels of ggplot objects the same size, but this behavior does not apply to the ggExtraPlot objects that ggMarginal creates.

For example, consider the following patchwork of ggplots (code below): four ggplots correctly sized

But once I add the marginal densities, the arrangement looks like this: four ggplots incorrectly sized

As you can see, the panels of each plot are no longer equally sized; rather, the entire objects are. I want the panel sizes of each ggplot in the second plot to be the same, as in the first plot.

Attempted solutions:

  • I tried to determine the width and height of the axes relative to the panel, because then I could use the widths and heights arguments in wrap_plot to set proper relative sizes of the ggMarginal-ized objects; unfortunately, I spent many hours unsuccessfully digging through the ggplotGrob output. For instance, I used grid::convertWidth(sum(ggplotGrob(p1)$widths), "in", TRUE) and grid::convertWidth(sum(ggplotGrob(p2)$widths), "in", TRUE) to attempt to calculate the widths of the plots in inches (p1 has a y-axis, p2 doesn't), but the results were vastly different: 0.87 and 0.19. It didn't make sense to me that simply removing the axis title, text, and ticks would so heavily decrease the width. Certainly setting the relative widths at around 4.5 and 1 wouldn't generate the proper patchwork plot!
  • I tried to use patchwork::get_dim and patchwork::set_dim to manually set the area for each subplot (using patchwork::area) but couldn't figure it out.
  • I also tried to create the patchwork first and then apply ggMarginal to each plot in the patchwork, but this didn't work.

I would appreciate advice on guaranteeing equal panel sizes for the four plots, ideally without relying on the actual size (i.e., in pixels) of the components. It would be awesome to see ggMarginal allow faceting and to see patchwork extend beyond simple ggplot objects!

b <- ggplot(mtcars, aes(x = disp, y = mpg)) +
  geom_point() +
  labs(x = "some\nlines",
       y = "many\nnew\nlines")

p1 <-  b +
  theme(axis.title.x = element_blank(),
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank())
p1m <- ggExtra::ggMarginal(p1, margins = "y")

p2 <- b +
  theme(axis.title = element_blank(),
        axis.text = element_blank(),
        axis.ticks = element_blank())
p2m <- ggExtra::ggMarginal(p2, margins = "y")

p3 <- b
p3m <- ggExtra::ggMarginal(p3, margins = "y")

p4 <- b +
  theme(axis.title.y = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank())
p4m <- ggExtra::ggMarginal(p4, margins = "y")

patchwork::wrap_plots(p1, p2, p3, p4, nrow = 2)      # this looks great!
patchwork::wrap_plots(p1m, p2m, p3m, p4m, nrow = 2)  # this doesn't :(

Solution

  • Instead of relying on ggMarginal one option would be to manually create your density plots and glue both your main and the density plots together using patchwork:

    library(ggplot2)
    library(patchwork)
    
    dp <- ggplot(mtcars, aes(y = mpg)) +
      geom_density() +
      theme_void()
    
    list(p1, dp, p2, dp, p3, dp, p4, dp) |> 
      wrap_plots(nrow = 2, widths = c(5, 1, 5, 1))