Understanding how the device list is read
I ran into a rather annoying issue with the list of open devices, trying to construct a function that saves a number of graphs for a list. Say we have following data :
Alist <- list(
X1 = data.frame(X=rnorm(10),Y=1:10),
X2 = data.frame(X=rnorm(10),Y=1:10),
X3 = data.frame(X=rnorm(10),Y=1:10)
)
and following function :
myPlotFunc <- function(x,save=F){
fnames <- paste(names(x),"pdf",sep=".")
for(i in 1:length(x)){
if(save){
pdf(fnames[i])
on.exit(dev.off(),add=T)
}
plot(x[[i]])
}
fnames
}
If I run fnames <- myPlotFunc(Alist,save=T)
, everything behaves normally and I get 3 pdf files names X1.pdf
to X3.pdf
. That is, if there's no graphics window open. If there is, then one of the pdfs is not closed, and all subsequent plots are added to the pdf until I explicitly call dev.off()
in the console. Like this :
plot(Alist[[1]])
fnames <- myPlotFunc(Alist,save=T)
myPlotFunc(Alist,save=F)
> dev.list()
pdf
4
If I add on.exit({print(dev.cur());dev.off()},add=T)
, I get following output :
> fnames <- myPlotFunc(Alist,save=T)
pdf
5
windows
2
pdf
3
So apparently it takes the list from the bottom up again to close everything it meets. So if there is a graphics window open, that is the next "current" device. Meaning that the second-last opened pdf-connection will not be closed by dev.off() any more, as there will be one short in the on.exit
call.
I got around it by changing my function to :
myPlotFunc <- function(x,save=F){
fnames <- paste(names(x),"pdf",sep=".")
devs <- NULL
on.exit(for(i in devs) dev.off(i), add=T)
for(i in 1:length(x)){
if(save){
pdf(fnames[i])
devs <- c(devs,dev.cur())
}
plot(x[[i]])
}
fnames
}
but this feels 开发者_JS百科rather awkward. Is there something I'm missing here, or a better way of getting around this?
disclaimer :
In case you're not aware, run dev.off()
after running the third code block. You can clean up easily by running unlink(fnames)
when you're done.
How about making help function to do one plot:
myPlotFunc <- function(x, save=FALSE) {
fnames <- paste(names(x), "pdf", sep=".")
plot_one <- function(xx, fname, save=save) {
if (save) {
pdf(fname)
on.exit(dev.off())
}
plot(xx)
}
for (i in 1:length(x)) plot_one(x[[i]], fnames[i], save)
fnames
}
One drastic solution might be to use graphics.off()
instead of trying to close devices your script opens. If this is just user code, then perhaps it doesn't matter if all graphics devics are closed upon exit?
Using this brutal approach seems to work:
myPlotFunc <- function(x,save = FALSE) {
fnames <- paste(names(x),"pdf",sep=".")
if(save)
on.exit(graphics.off(),add = TRUE)
for(i in 1:length(x)) {
if(save) {
pdf(fnames[i])
}
plot(x[[i]])
}
fnames
}
An alternative is just to list all devices when on.exit()
gets called, select out the pdf
ones and close them. This function implements this and seems to have the desired behaviour.
myPlotFunc2 <- function(x,save = FALSE) {
fnames <- paste(names(x), "pdf", sep=".")
if(save) {
on.exit(foo <- lapply(dev.list()[grepl("pdf", names(dev.list()))],
dev.off),
add = TRUE)
}
for(i in 1:length(x)) {
if(save) {
pdf(fnames[i])
}
plot(x[[i]])
}
fnames
}
It seems the lowest numbered device is the one that R activates after a call to dev.off()
, and that will be the on-screen device in the setting you describe, and hence the behaviour your report.
精彩评论