Michael Friendly
2011-Oct-27 14:13 UTC
[R] minimizing device-dependent code in graphics scripts
Here is a common scenario that I and probably others often face: - I write a script, test.R, to produce some graphs, viewing them on-screen and tinkering until they look right - I want to save image files (e.g., .png), so I wrap each plot in device driver calls, e.g., png(file='test01.png', width=600, height=600); plot(1:10) dev.off() png(file='test02.png', width=600, height=600); plot(10:1) dev.off() ... - Then I want to include those graphs in a document, so maybe I want them in .pdf or .eps format. So I have to modify all the device calls to pdf() or postscript(), changing the filenames and perhaps other options. If I want those graphs to be squentially numbered with filenames like 'test%02d' and insert a new graph in the sequence, I have to edit all the filenames after the insertion. There has to be an easier way. (I know I could use Sweave, but that introduces another layer that I want to avoid while I'm developing plots.) This contrasts strongly with what I've done for many years with SAS: I use *no* device-dependent code in .sas files, but instead use a collection of device-independent macros that respond to a global macro variable, DEVTYP. On linux, a custom (perl) script sets this as an environment variable used by these macros, so I can run test.sas at the command line via % alias s=mysas % s -b -d eps test % s -d pdf test % s -v -d png test and get test1.eps, test2.eps, ... etc. where the basename of the image files is automatically set to something like 'test%01d'. The -v option opens each of the generated graphics files in an appropriate viewer (gs, display, xpdf, etc.) The -b option for .eps files runs a bbfix script to adjust bounding boxes on .eps files. I don't need anything this elaborate for R, but it's a long way from what I do with SAS vs what I do currently with R. Perhaps other have some partial solutions for this general problem they'd be willing to share. For what it's worth, here is an initial sketch of one approach, using two general functions, img() and img.off(). Perhaps others could help improve it. ############ img.R ################### # shorthand for eps() eps <- function(file="Rplot.eps", horizontal=FALSE, paper="special", height=6, width=6, ...) { postscript(file=file, onefile=FALSE, horizontal=horizontal, paper=paper, height=height, width=width,...) } img <- function(file, type, height=6, width=6, res=100, units="in", ...) { # handle image types types <- c("bmp", "eps", "jpg", "pdf", "png") if (missing(type)) { if (exists("imgtype")) { if (is.null(imgtype)) return() else type <- imgtype } } else { t <- match(type, types, nomatch=0) if(t > 0) type <- types[t] else stop("unknown file type") } if (exists("imgnum") imgnum <<- imgnum+1 else imgnum <<- 1 # TODO: Handle global imgnum in filename if (missing(file)) { file <- if(exists("imgname")) paste(imgname, '%03d', sep='') else "Rplot%03d" } filename <- paste(file, '.', type, sep='') switch(type, bmp = bmp(filename, height=height, width=width, res=res, units=units, ...), eps = eps(filename, height=height, width=width, ...), jpg = jpeg(filename, height=height, width=width, res=res, units=units, ...), pdf = pdf(filename, height=height, width=width, ...), png = png(filename, height=height, width=width, res=res, units=units, ...) ) } img.off <- function() { if (exists("imgtype") & !is.null(imgtype)) dev.off() } TESTME <- FALSE if(TESTME) { # set global image name and starting number imgname <- 'test' imgnum <- 1 imgtype <- NULL # screen output img() plot(1:10, main=paste("imgtype:", imgtype)) img.off() imgtype <- "pdf" img() plot(10:1, main=paste("imgtype:", imgtype)) img.off() } -- Michael Friendly Email: friendly AT yorku DOT ca Professor, Psychology Dept. York University Voice: 416 736-5115 x66249 Fax: 416 736-5814 4700 Keele Street Web: http://www.datavis.ca Toronto, ONT M3J 1P3 CANADA
Duncan Murdoch
2011-Oct-27 14:32 UTC
[R] minimizing device-dependent code in graphics scripts
On 27/10/2011 10:13 AM, Michael Friendly wrote:> Here is a common scenario that I and probably others often face: > > - I write a script, test.R, to produce some graphs, viewing them > on-screen and tinkering until they look right > - I want to save image files (e.g., .png), so I wrap each plot in device > driver calls, e.g., > > png(file='test01.png', width=600, height=600); > plot(1:10) > dev.off() > > png(file='test02.png', width=600, height=600); > plot(10:1) > dev.off() > ... > > > - Then I want to include those graphs in a document, so maybe I want > them in .pdf or .eps format. So I have > to modify all the device calls to pdf() or postscript(), changing the > filenames and perhaps other options. > If I want those graphs to be squentially numbered with filenames like > 'test%02d' and insert a new graph > in the sequence, I have to edit all the filenames after the insertion. > There has to be an easier way. (I know I could use Sweave, but that > introduces another layer that I want to > avoid while I'm developing plots.)I use Sweave, more or less as you describe at the top: I fiddle with the graphs on screen first, then when they're fine, I put the code into the document. Mostly the documents use PDF figures, but it's a global change at the top if I want something else. Since all the fiddling is done on screen, I don't have the issue of Sweave overhead being a problem during development (and it's generally pretty low overhead, as long as I don't put long computations into the documents). But if I wanted to avoid device calls in scripts, I'd probably do what you did below. Duncan Murdoch> This contrasts strongly with what I've done for many years with SAS: I > use *no* device-dependent code in .sas > files, but instead use a collection of device-independent macros that > respond to a global macro variable, DEVTYP. > On linux, a custom (perl) script sets this as an environment variable > used by these macros, so I can run test.sas at the > command line via > > % alias s=mysas > % s -b -d eps test > % s -d pdf test > % s -v -d png test > > and get test1.eps, test2.eps, ... etc. where the basename of the image > files is automatically set to something > like 'test%01d'. The -v option opens each of the generated graphics > files in an appropriate viewer (gs, display, xpdf, etc.) > The -b option for .eps files runs a bbfix script to adjust bounding > boxes on .eps files. > > I don't need anything this elaborate for R, but it's a long way from > what I do with SAS vs what I do currently with R. > Perhaps other have some partial solutions for this general problem > they'd be willing to share. > > For what it's worth, here is an initial sketch of one approach, using > two general functions, img() and img.off(). > Perhaps others could help improve it. > > ############ img.R ################### > # shorthand for eps() > eps<- function(file="Rplot.eps", horizontal=FALSE, paper="special", > height=6, width=6, ...) { > postscript(file=file, onefile=FALSE, horizontal=horizontal, > paper=paper, height=height, width=width,...) > } > > img<- function(file, type, > height=6, width=6, res=100, units="in", ...) { > > # handle image types > types<- c("bmp", "eps", "jpg", "pdf", "png") > if (missing(type)) { > if (exists("imgtype")) { > if (is.null(imgtype)) return() else type<- imgtype > } > } > else { > t<- match(type, types, nomatch=0) > if(t> 0) type<- types[t] else stop("unknown file type") > } > > if (exists("imgnum") imgnum<<- imgnum+1 > else imgnum<<- 1 > > # TODO: Handle global imgnum in filename > if (missing(file)) { > file<- if(exists("imgname")) paste(imgname, '%03d', sep='') > else "Rplot%03d" > } > filename<- paste(file, '.', type, sep='') > switch(type, > bmp = bmp(filename, height=height, width=width, res=res, > units=units, ...), > eps = eps(filename, height=height, width=width, ...), > jpg = jpeg(filename, height=height, width=width, res=res, > units=units, ...), > pdf = pdf(filename, height=height, width=width, ...), > png = png(filename, height=height, width=width, res=res, > units=units, ...) > ) > } > > img.off<- function() { > if (exists("imgtype")& !is.null(imgtype)) dev.off() > } > > > TESTME<- FALSE > > if(TESTME) { > > # set global image name and starting number > imgname<- 'test' > imgnum<- 1 > imgtype<- NULL # screen output > > img() > plot(1:10, main=paste("imgtype:", imgtype)) > img.off() > > imgtype<- "pdf" > img() > plot(10:1, main=paste("imgtype:", imgtype)) > img.off() > > } > > > >