Skip to content
Prev 275666 / 398506 Next

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()

}