5

I'm displaying long figures in a markdown report.

These are long because they use ggplot2::facet_wrap so their height depend on the data, which is not constant.

I can set the figure.height parameter of the chunk but then it's fixed and my report looks bad. Is their a way around this ?

Example :

---
title: "title"
author: "author"
date: '`r Sys.Date()`'
output: html_document
---

```{r, figure.height=40}
library(dplyr)
library(ggplot2)
iris %>%
  mutate_at("Sepal.Length",cut, 5) %>%
  mutate_at("Sepal.Width",cut,2) %>%
  group_by_at(c(1,2,5)) %>%
  summarize_at("Petal.Length",mean) %>%
  ggplot(aes(Species, Petal.Length)) +
  geom_col() +
  facet_wrap(Sepal.Length ~ Sepal.Width,ncol=2)
```
moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
  • Nice question! I was once struggling with something similar, and then I stumbled over: [Dynamic height and width for knitr plots](https://stackoverflow.com/questions/15365829/dynamic-height-and-width-for-knitr-plots). "Say you want to output a bunch of plots using a loop but you want them to each have different sizes", "Or let the data define the sizes". Maybe, maybe useful for you? Perhaps also: [Print a list of dynamically-sized plots in knitr](https://stackoverflow.com/questions/33701038/print-a-list-of-dynamically-sized-plots-in-knitr) – Henrik Jun 27 '18 at 15:27
  • That's very useful thanks a lot, that's almost a duplicate actually. I had no idea I could just use a variable instead of a fixed parameter. But to spice it up a bit and have my report look REALLY good I'll add a request. If I just set my height to `n * single_height` the figures will still be more or less stretched because of the margins and label sizes that are fixed, how can I account for that ? – moodymudskipper Jun 27 '18 at 15:33
  • 1
    I was just procrastinating from a review I have to finish, I need to get back to that... Sorry, not able to help you more right now. Good luck! – Henrik Jun 27 '18 at 15:36
  • 1
    Good luck with the review :) – moodymudskipper Jun 27 '18 at 15:37

2 Answers2

2

I had a similar issue and was not able to get Peter's solution to work. From what I'm able to gather, eval.after does not work with fig.height.

But thanks to Peter's example, I was able to find a work-around:

---
author: "author"
date: '`r Sys.Date()`'
output: html_document
---

```{r setup}
library(dplyr)
library(ggplot2) 
library(knitr)

FACET_HEIGHT <- 3.4
```

In chunk 1:  First, create the ggplot.
Then, use `ggplot_build` to create a new variable called `adaptive_figure_height`.
Finally, use knitr::opts_chunk$set to update the chunk option `fig.height` to better suit your ggplot.
```{r}
g <- 
  iris %>%
  mutate_at("Sepal.Length",cut, 5) %>%
  mutate_at("Sepal.Width",cut,2) %>%
  group_by_at(c(1,2,5)) %>%
  summarize_at("Petal.Length",mean) %>%
  ggplot(aes(Species, Petal.Length)) +
  geom_col() +
  facet_wrap(Sepal.Length ~ Sepal.Width, ncol = 2)

adaptive_fig_height <- FACET_HEIGHT * max(ggplot_build(g)$layout$layout$ROW)
 
opts_chunk$set( fig.height = adaptive_fig_height )
```

In chunk 2: Plot the ggplot. 
If needed, you can revert `fig.height` back to a default value.
```{r }
g
opts_chunk$set( fig.height = 7 )
```
 
Repeat the setup in chunk 1 and 2 if you have multiple long plots with differing heights.
E. Nygaard
  • 104
  • 6
  • This is a neat trick. But does it account for plot height in title, subtitle, caption or legends? – mzuba Sep 14 '22 at 09:12
  • You have to do some trial and error with different values of `FACET_HEIGHT` to get a plot to your liking. If I had multiple ggplots where some plots had titles, captions etc., I would probably use a different value for `FACET_HEIGHT` for each ggplot and define them in the first chunk: `FACET_HEIGHT_plot1 = 3.2`, `FACET_HEIGHT_plot2 = 1.8` etc. Automating the process of determining the "appropriate" adaptive figure height for many different types of plots - some with long captions and legends, and some not - would probably not be worth the effort for me personally. – E. Nygaard Oct 03 '22 at 17:58
1

To go along with the n * single_height idea: you can use the chunk option eval.after so that the fig.width and fig.height options will be evaluated after the rest of the chunk is evaluated and then use the ggplot_build to pull apart a ggplot object and determine the number of rows and columns used in the facets.

For example:

---
author: "author"
date: '`r Sys.Date()`'
output: html_document
---

```{r setup}
library(dplyr)
library(ggplot2) 
library(knitr)

FACET_HEIGHT = 3.4
FACET_WIDTH  = 5

opts_chunk$set(out.width = "80%",
               out.height = "80%",
               eval.after = c("fig.height", "fig.width"))
```

For the example we'll have one basic plot to which we will set different facets.
```{r}
g <- 
  iris %>%
  mutate_at("Sepal.Length",cut, 5) %>%
  mutate_at("Sepal.Width",cut,2) %>%
  group_by_at(c(1,2,5)) %>%
  summarize_at("Petal.Length",mean) %>%
  ggplot(aes(Species, Petal.Length)) +
  geom_col()
```

A graphic with two columns
```{r fig1, fig.height = FACET_HEIGHT * max(ggplot_build(g1)$layout$layout$ROW), fig.width = FACET_WIDTH * max(ggplot_build(g1)$layout$layout$COL)}
g1 <- g + facet_wrap(Sepal.Length ~ Sepal.Width, ncol = 2)
g1
```


A graphic with two rows
```{r fig2, fig.height = FACET_HEIGHT * max(ggplot_build(g2)$layout$layout$ROW), fig.width = FACET_WIDTH * max(ggplot_build(g2)$layout$layout$COL)}
g2 <- g + facet_wrap(Sepal.Length ~ Sepal.Width, nrow = 2)
g2
```

A screenshot of the resulting html is:

enter image description here

Some fine tuning of the image width and height will be needed, but this should be a good starting point.

Peter
  • 7,460
  • 2
  • 47
  • 68