rstudio / rmarkdown

Dynamic Documents for R
https://rmarkdown.rstudio.com
GNU General Public License v3.0
2.88k stars 977 forks source link

How to restore par() when knitting to html while the fig.width is small? #2261

Open DawnEve opened 2 years ago

DawnEve commented 2 years ago

I found this bug weeks ago, which I can't restore par() in Rmarkdown sometimes when the fig.width is too small.

If I want this small fig.width size and to restore par() in the same trunk like the code shown below, is it possible?

This bug can be passed when setting fig.width=2.2, fig.height=1 larger or removing opar=par(no.readonly = T), or puting them in two separate trunks. I used the 3rd solution now, so this bug is not urgent.

CentOS7 + R4.1.1 + rmarkdown 2.1.1
---
title: "test4"
author: "xx"
date: "`r Sys.time()`"
output:
  html_document:
    toc: true
---

# test1

```{r, fig.width=2.2, fig.height=1}
opar=par(no.readonly = T)

par( mar=c(0,0,0,0))
plot(iris$Sepal.Length)

par(opar)

plot(c(2)) #restore mannually in the same trunk

test2

plot(iris$Sepal.Length) # restore par() by Rmarkdown, when go to a new trunk

This code may alert errors when knitting.

Error in par(opar) : invalid value specified for graphical parameter "pin" Calls: <Anonymous> ... withCallingHandlers -> withVisible -> eval -> eval -> par Execution halted

cderv commented 2 years ago

I'll look at that.

Just to share that I'll edit your post so that guidelines are followed for readability: https://yihui.org/issue/#please-format-your-issue-correctly

cderv commented 2 years ago

I can't really explain it but it seems that the R code is not really valid itself

About the pin in par()

pin: The current plot dimensions, (width, height), in inches.

When setting fig.width and fig.height in a chunk, it will set the width and height of the graphic device. We can emulate code this way and it will throw an error even outside of knitr, even 2 errors

# Temp plot file
plot_file <- tempfile(fileext = ".png")

# Before opening device
par(no.readonly = TRUE)$pin
#> [1] 5.759999 3.159999

# Knitr will open a png device using the size provided
grDevices::png(filename = plot_file,
               width = 2.2,
               height = 1,
               units = "in",
               res = 72)

# New pin value following the size
opar <- par(no.readonly = TRUE)
opar$pin
#> [1]  0.9544431 -0.8400014

# It is negative - not sure why

# Changing par 
par(no.readonly = TRUE)$mar
#> [1] 5.1 4.1 4.1 2.1
par(mar=c(0,0,0,0))
par(no.readonly = TRUE)$mar
#> [1] 0 0 0 0

# Ploting work
plot(iris$Sepal.Length)

# Trying to restore par - issue with `par`, probably because negative dimention is invalid
par(opar)
#> Error in par(opar): invalid value specified for graphical parameter "pin"

# Trying to plot with reset par - margin has been reset, but it is too large as the figure is too small
# so error
plot(2)
#> Error in plot.new(): figure margins too large

dev.off()
#> png 
#>   2

unlink(plot_file)

When you set the dimension in chunk option to be greater, this won't probably happen because the values are ok in par()

But as you found knitr will open a new graphic device for each chunk by default, so if you need to reset some par define in one chunk, it is recommended to open a new graphic device.

So I am not sure it is a bug in rmarkdown or knitr considering that doing the same in R base would lead to the same result.

Not sure why pin would be negative as it is supposed to be a dimension.

@yihui do you have more insights on this behavior ?

yihui commented 2 years ago

I don't know why pin could be negative, either. It sounds like a bug of base R. Perhaps try to report it to R core? https://www.r-project.org/bugs.html

cderv commented 2 years ago

Perhaps try to report it to R core?

Ok I'll look into that. Never done it before but there is a first for everything ! 😄

DawnEve commented 2 years ago

Good idea. As I'm not going that deep in R, it's a good idea for senior developers like you to report and discuss this issue.

I just waiting, and alternative solutions in Rmarkdown is acceptable for this situation now.