New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ggsave() can change active graphics device #2363

Closed
wch opened this Issue Dec 8, 2017 · 7 comments

Comments

Projects
None yet
4 participants
@wch
Member

wch commented Dec 8, 2017

If there is more than one graphics device open, calling ggsave() can change which one is active.

For example, in RStudio:

library(ggplot2)

dev.new()
dev.new()
dev.cur()
#> quartz 
#>      4 

p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
ggsave('test.png', p)
dev.cur()
#> RStudioGD 
#>         2 

In R at the terminal:

library(ggplot2)

dev.new()
dev.new()
dev.cur()
#> quartz 
#>      3 

p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
ggsave('test.png', p)
dev.cur()
#> quartz 
#>      2 
@dracodoc

This comment has been minimized.

dracodoc commented Dec 8, 2017

A simple fix would be saving current device and restoring it.

cur_dev <- dev.cur()
ggsave("plot.png", g)
dev.set(cur_dev)

To fix it inside ggsave we can do something like this

cur_dev <- dev.cur()
dev(file = filename, width = dim[1], height = dim[2], ...)
on.exit(utils::capture.output({
  grDevices::dev.off()
  dev.set(cur_dev)
}))
grid.draw(plot)
@basjacobs93

This comment has been minimized.

basjacobs93 commented Jul 13, 2018

On Ubuntu 17.10, when putting the below code in an Rscript and running it from the command line, the graphics device is still changed.

library(ggplot2)

print(dev.cur())
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
ggsave('test.png', p)
print(dev.cur())
$ Rscript test.R
null device 
          1 
Saving 7 x 7 in image
pdf 
  2 

It also creates an empty Rplots.pdf file next to test.png. In fact, such a file is already created when simply running

old_dev <- grDevices::dev.cur()
grDevices::pdf(file = "test.pdf")
grDevices::dev.off()
grDevices::dev.set(old_dev)

as an Rscript from the command line. I therefore believe this is also closely related to #2752 .

getOption("device") gives a function which is grDevices::pdf, and the environment variable R_DEFAULT_DEVICE is empty.

Running this from within Rstudio does give the expected behavior.

@clauswilke

This comment has been minimized.

Member

clauswilke commented Jul 13, 2018

I'm reopening this issue, since I think I know what's going on. And I agree #2752 is likely the same problem.

This code tries to save the current graphics device and then restore it once the plot is saved:

ggplot2/R/save.r

Lines 57 to 62 in 79e8b45

old_dev <- grDevices::dev.cur()
dev(filename = filename, width = dim[1], height = dim[2], ...)
on.exit(utils::capture.output({
grDevices::dev.off()
grDevices::dev.set(old_dev)
}))

The problem is this doesn't work when no device is currently open. When no device is open, dev.cur() returns 1. However, when dev.set() is called with an argument of 1, it opens a new device (!):

library(grDevices)
dev.off()
#> null device 
#>           1
dev.list()
#> NULL
dev.cur()
#> null device 
#>           1
dev.set(1)
#> null device 
#>           1
dev.list()
#> pdf 
#>   2

(This is documented behavior: "dev.set makes the specified device the active device. If there is no device with that number, it is equivalent to dev.next. If which = 1 it opens a new device and selects that.")

The solution I think is to replace line 61 in the code quoted above with:

if (old_dev > 1) grDevices::dev.set(old_dev)
@clauswilke

This comment has been minimized.

Member

clauswilke commented Jul 13, 2018

I made a pull request. It would be great if somebody could test it. Install from github via:

devtools::install_github("clauswilke/ggplot2@issue-2363-ggsave")
@basjacobs93

This comment has been minimized.

basjacobs93 commented Jul 16, 2018

I can confirm that the pull request @clauswilke made works for me.

$ Rscript test.R
null device 
          1 
Saving 7 x 7 in image
null device 
          1 
@basjacobs93

This comment was marked as off-topic.

basjacobs93 commented Jul 16, 2018

I am not sure if this belongs here or at the gridExtra repository, but I do get an Rplots.pdf file (and changed device) when running this file as an Rscript:

library(ggplot2)
library(gridExtra)

print(dev.cur())
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + theme_minimal()
g <- arrangeGrob(grobs = list(p))
print(dev.cur())
→ Rscript test.R
null device 
          1 
pdf 
  2 

whereas if I change the theme to theme_void, I do not get this behavior:

→ Rscript test.R
null device 
          1 
null device 
          1 

This still happens when using the fix proposed by @clauswilke.
I do not really know how to debug this any further.

@clauswilke

This comment was marked as off-topic.

Member

clauswilke commented Jul 16, 2018

@basjacobs93 It's one of those bugs where it's not clear whose fault it is. The problem is that grid can't draw text without having a graphics device open, and if none is open it opens one. theme_void() works because it doesn't draw text.

As a workaround, you could add pdf(NULL) to your code before you call arrangeGrob(), that should provide the needed graphics device without creating a file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment