gridsvg does not work when used inside a function - r

I want to define a plot saving function that uses the gridsvg device from the package gridSVG.
library(ggplot2)
library(gridExtra)
mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5),
labels=c("3gears","4gears","5gears"))
mtcars$am <- factor(mtcars$am,levels=c(0,1),
labels=c("Automatic","Manual"))
mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8),
labels=c("4cyl","6cyl","8cyl"))
myPlot <- qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5),
main="Distribution of Gas Milage", xlab="Miles Per Gallon",
ylab="Density")
savePlot <- function(filename, plot, plotWidth = 15, plotHeight = 10){
gridSVG:::gridsvg(name = filename, width = plotWidth, height = plotHeight)
print(plot)
dev.off(which = dev.cur())
}
However if I then try to use the function it does not work. An error results:
savePlot("~/Desktop/myplot.svg", myPlot)
Show Traceback
Rerun with Debug
Error in eval(expr, envir, enclos) : object 'filename' not found
However if I do those steps from the console it works:
gridSVG::gridsvg(name = "~/Desktop/myPlot.svg", width = 15, height = 10)
myPlot
dev.off()
Is there a way I might be able to use the gridsvg function from within another function?
I wonder if I might be able to do it with eval from some environment.
Thanks,
Ben.

Here's a roundabout, but slightly more principled, way without sticking everything in the global environment (inspired by the scoping discussion in R Inferno):
library(gridSVG)
savePlot <- function(filename, plot, plotWidth = 15, plotHeight = 10){
gridsvg(sys.frame(1))
print(plot)
grid.export(filename)
grDevices::dev.off(which = dev.cur())
}
sys.frame(1) gives us the evaluation frame of the parent context (there's an ok explanation here for all variations on functions that access the call stack).
I separated out the call to grid.export() from the call to dev.off(), because essentially all the dev.off from gridSVG does is call grid.export, then call the grDevices::dev.off. This also lets us explicitly feed the file name to grid.export.

How curious. gridsvg seems to have an eval(fncall[[i]]) step where it walks through all the arguments and assigns them, and it must be looking in the wrong environment or something?? I am not sure if this is a problem with the gridSVG package; eval-semantics always confuse me.
Here's a workaround: if you make sure the argument -values- get passed to gridsvg (rather than the argument names) it works, though I agree this isn't particularly elegant. And you have to explicitly library(gridSVG).
library(gridSVG)
savePlot <- function(filename, plot, plotWidth = 15, plotHeight = 10){
eval(call('gridsvg', name=filename, width=plotWidth, height=plotHeight))
print(plot)
dev.off(which = dev.cur())
}
All it does is essentially call gridsvg with width=15 rather than width=plotWidth and so on.

Related

Using ggsave with a pipe

I can save a plot with ggsave after I stored it, but using it in a pipeline I get the following error. I wish to plot and save in the same (piped) command.
no applicable method for 'grid.draw' applied to an object of class "c('LayerInstance', 'Layer', 'ggproto', 'gg')"
I know ggsave's arguments are first the filename, and then the plot, but switching this in a wrapper does not work. Also, using 'filename=' and 'plot=' in the ggsave command does not work.
library(magrittr)
library(ggplot2)
data("diamonds")
# my custom save function
customSave <- function(plot){
ggsave('blaa.bmp', plot)
}
#This works:
p2 <- ggplot(diamonds, aes(x=cut)) + geom_bar()
p2 %>% customSave()
# This doesn't work:
ggplot(diamonds, aes(x=cut)) + geom_bar() %>% customSave()
# and obviously this doesn't work either
ggplot(diamonds, aes(x=cut)) + geom_bar() %>% ggsave('plot.bmp')
As akrun pointed out, you need to wrap all of your ggplot in parentheses. You can also use the dot notation to pass an object to a function parameter other than the first in a magrittr pipe stream:
library(magrittr)
library(ggplot2)
data("diamonds")
(
ggplot(diamonds, aes(x=cut)) +
geom_bar()
) %>%
ggsave("plot.png", . , dpi = 100, width = 4, height = 4)
Update: The following code no longer works. See the tidyverse GitHub issue and another StackOverflow question for the official solution.
Original answer that NO LONGER WORKS:
If you want to plot and save in one line, try this
ggsave('plot.bmp') ```
If you don't want to show the plot, simply put `p <- ` at the
beginning.
If you have a custom save function, you can also do this
``` mysave <- function(filename) { ggsave(file.path("plots",
paste0(filename, ".png")),
width = 8, height = 6, dpi = 300) } ```
and simply replace `ggsave('plot.bmp')` with `mysave('plot')` in the
snippet above.
I found this usage by accident but haven't found any documentation.

Save plots as R objects and displaying in grid

In the following reproducible example I try to create a function for a ggplot distribution plot and saving it as an R object, with the intention of displaying two plots in a grid.
ggplothist<- function(dat,var1)
{
if (is.character(var1)) {
var1 <- which(names(dat) == var1)
}
distribution <- ggplot(data=dat, aes(dat[,var1]))
distribution <- distribution + geom_histogram(aes(y=..density..),binwidth=0.1,colour="black", fill="white")
output<-list(distribution,var1,dat)
return(output)
}
Call to function:
set.seed(100)
df <- data.frame(x = rnorm(100, mean=10),y =rep(1,100))
output1 <- ggplothist(dat=df,var1='x')
output1[1]
All fine untill now.
Then i want to make a second plot, (of note mean=100 instead of previous 10)
df2 <- data.frame(x = rep(1,1000),y = rnorm(1000, mean=100))
output2 <- ggplothist(dat=df2,var1='y')
output2[1]
Then i try to replot first distribution with mean 10.
output1[1]
I get the same distibution as before?
If however i use the information contained inside the function, return it back and reset it as a global variable it works.
var1=as.numeric(output1[2]);dat=as.data.frame(output1[3]);p1 <- output1[1]
p1
If anyone can explain why this happens I would like to know. It seems that in order to to draw the intended distribution I have to reset the data.frame and variable to what was used to draw the plot. Is there a way to save the plot as an object without having to this. luckly I can replot the first distribution.
but i can't plot them both at the same time
var1=as.numeric(output2[2]);dat=as.data.frame(output2[3]);p2 <- output2[1]
grid.arrange(p1,p2)
ERROR: Error in gList(list(list(data = list(x = c(9.66707664902549, 11.3631137069225, :
only 'grobs' allowed in "gList"
In this" Grid of multiple ggplot2 plots which have been made in a for loop " answer is suggested to use a list for containing the plots
ggplothist<- function(dat,var1)
{
if (is.character(var1)) {
var1 <- which(names(dat) == var1)
}
distribution <- ggplot(data=dat, aes(dat[,var1]))
distribution <- distribution + geom_histogram(aes(y=..density..),binwidth=0.1,colour="black", fill="white")
plot(distribution)
pltlist <- list()
pltlist[["plot"]] <- distribution
output<-list(pltlist,var1,dat)
return(output)
}
output1 <- ggplothist(dat=df,var1='x')
p1<-output1[1]
output2 <- ggplothist(dat=df2,var1='y')
p2<-output2[1]
output1[1]
Will produce the distribution with mean=100 again instead of mean=10
and:
grid.arrange(p1,p2)
will produce the same Error
Error in gList(list(list(plot = list(data = list(x = c(9.66707664902549, :
only 'grobs' allowed in "gList"
As a last attempt i try to use recordPlot() to record everything about the plot into an object. The following is now inside the function.
ggplothist<- function(dat,var1)
{
if (is.character(var1)) {
var1 <- which(names(dat) == var1)
}
distribution <- ggplot(data=dat, aes(dat[,var1]))
distribution <- distribution + geom_histogram(aes(y=..density..),binwidth=0.1,colour="black", fill="white")
plot(distribution)
distribution<-recordPlot()
output<-list(distribution,var1,dat)
return(output)
}
This function will produce the same errors as before, dependent on resetting the dat, and var1 variables to what is needed for drawing the distribution. and similarly can't be put inside a grid.
I've tried similar things like arrangeGrob() in this question "R saving multiple ggplot2 plots as R-object in list and re-displaying in grid " but with no luck.
I would really like a solution that creates an R object containing the plot, that can be redrawn by itself and can be used inside a grid without having to reset the variables used to draw the plot each time it is done. I would also like to understand wht this is happening as I don't consider it intuitive at all.
The only solution I can think of is to draw the plot as a png file, saved somewhere and then have the function return the path such that i can be reused - is that what other people are doing?.
Thanks for reading, and sorry for the long question.
Found a solution
How can I reference the local environment within a function, in R?
by inserting
localenv <- environment()
And referencing that in the ggplot
distribution <- ggplot(data=dat, aes(dat[,var1]),environment = localenv)
made it all work! even with grid arrange!

Input must be grobs Error when using do.call()

I am trying to create four parallel coordinate plots into a big figure. Below is just toy data to make it reproducible so it has no particular meaning other than mimicking my real data of meaning.
library(GGally)
library(gridExtra)
library(ggplot2)
cNum=4
x = data.frame(a=2*runif(100)-1,b=2*runif(100)-1,c=2*runif(100)-1,d=2*runif(100)-1,e=2*runif(100)-1)
plot_i = vector("list", length=cNum)
for (i in 1:4){
x = x + i
plot_i[[i]] = ggparcoord(x, columns=1:5, alphaLines=0.5)
do.call("grid.arrange", c(plot_i, ncol=1))
}
Here, it seems that the error occurs on the first iteration of the do.call part. I get an error:
Error in arrangeGrob(..., as.table = as.table, clip = clip, main = main, :
input must be grobs!
I see this is a well-searched Error, although the solution seems to be dependent on the context. For instance, here, the user misused ncol (Error with grid.arrange:input must be grobs) but that does not seem to be my situation.

absoluteGrob not found (ggplot2)

The function absoluteGrob {ggplot2} shows a behavior that I am not able to debug. I have ggplot2 installed and I can see the help page by ?absoluteGrob.
However, R does not find it when I try to use it:
> absoluteGrob
Error: object 'absoluteGrob' not found
More particularly, I try to execute the following code (from this answer to plot some graphs as x-labels):
library(grid)
library(ggplot2)
library(grImport)
library(igraph)
npoints <- 3
y <- rexp(npoints)
x <- seq(npoints)
pics <- vector(mode="list", length=npoints)
for(i in 1:npoints){
fileps <- paste0("motif",i,".ps")
filexml <- paste0("motif",i,".xml")
# Postscript file
postscript(file = fileps, fonts=c("serif", "Palatino"))
plot(graph.ring(i), vertex.label.family="serif", edge.label.family="Palatino")
dev.off()
# Convert to xml accessible for symbolsGrob (my_axis)
PostScriptTrace(fileps, filexml)
pics[i] <- readPicture(filexml)
}
my_axis <- function () {
function(label, x = 0.5, y = 0.5, ...) {
absoluteGrob(
do.call("gList", mapply(symbolsGrob, pics[label], x, y, SIMPLIFY = FALSE)),
height = unit(1.5, "cm")
)
}
}
qplot(factor(c("a", "b", "c")), 1:npoints) + scale_x_discrete(labels= my_axis())
But I get the error:
Error in scale$labels(breaks) : could not find function "absoluteGrob"
Any help (or alternatives) is welcome.
ggplot2 version:
ggplot2_1.0.1
Edit
Even in the simple case...
It does not work:
library(ggplot2)
absoluteGrob
It does:
library(ggplot2)
ggplot2:::absoluteGrob
The answer you linked to in your post was posted 3 years ago as of this posting, and many things in ggplot2 have changed since then. At that point, ggplot2 version 0.9.0 had not yet been released.
According to the 1.0.0 documentation for absoluteGrob, it's still experimental, which means that it was certainly experimental at the time of the linked answer. At that point it was likely exported from the ggplot2 namespace and thus available to the user. That's why the linked answer worked at the time.
However, as of version 1.0.1, it is not exported from the ggplot2 namespace. So while you're able to view the source and documentation with ggplot2:::absoluteGrob (which works for non-exported objects) and ?absoluteGrob, you will not be able to use it, even by explicitly specifying the namespace via ggplot2::absoluteGrob.
According to the source, it simply calls gTree(), which is from the grid package, with cl="absoluteGrob". You can try that in place of calling absoluteGrob() directly. For example, try the following, which will hopefully mimic the desired behavior from absoluteGrob():
grlist <- do.call("gList", mapply(symbolsGrob, pics[label], x, y, SIMPLIFY = FALSE))
gTree(children = grlist,
cl = "absoluteGrob",
height = unit(1.5, "cm"),
width = NULL,
xmin = NULL,
ymin = NULL,
vp = NULL)

How to suppress qplot's binwidth warning inside a function?

I am writing a function that uses qplot() to draw a histogram, for example,
> library(ggplot2)
> d=rnorm(100)
> myfun=function(x) qplot(x)
Running it gives a warning:
> myfun(d)
stat_bin: binwidth defaulted to range/30. Use 'binwidth = x' to adjust this.
To suppress the warning, I tried computing the binwidth myself, but this gives an error and doesn't plot:
> myfun=function(x) print(qplot(x, binwidth=diff(range(x))/30))
> myfun(d)
Error in diff(range(x)) : object 'x' not found
I have two related questions:
What is going on here? Why is object 'x' not found?
How can I write the function so the warning is not generated?
Thanks!
To attempt to clear up some confusion, this construct does not prevent the binwidth warnings/messages to appear:
suppressMessages(p <- ggplot(...))
print(p)
But this does:
p <- ggplot(...)
suppressMessages(print(p))
As Hadley's comment points out, lazy evaluation prevents the stat_* functions from actually running until they need to at print-time.
I can't explain the why of this one (Hadley may swing by and do so) but using ggplot instead of qplot solves the problem:
d <- data.frame(v1 = rnorm(100))
myfun <- function(x){
p <- ggplot(data = x, aes(x = v1)) +
geom_histogram(binwidth = diff(range(x$v1))/30)
print(p)
}
Doing it this way I get no warning message. Also, using ggplot and removing the binwidth = ... portion in geom_histogram makes the warning reappear, but then suppressMessages works as expected as well.
I suspect this has to do with namespaces or environments and when/where qplot and ggplot are evaluating arguments. But again, that's just a guess...
As they say on TV "Had this been a real warning you would have been given directions from your local authorities."
Since it wasn't a warning then my original answer didn't cause it to error out. This is what I should have written:
options(warnings= -1)
<do something> # no warnings
options(warnngs=1)
<business as usual>
But it wasn't a warning but a message to the console. Here's how to stop it:
con=file("temp.fil", "w")
sink(con, type="message")
library(ggplot2)
d=rnorm(100)
myfun=function(x) qplot(x)
myfun(d)
sink( type="message")

Resources