Thursday, January 17, 2013

Uniform R code for opening, saving graphs in Windows and Mac OS

[See follow-up here.]

A big frustration when trying to create R code that works across Windows and Mac OS is that the R commands for opening graphics windows, and for saving their contents, are different in the two operating systems. In Windows, the functions windows() and savePlot() do the job nicely. But in Mac OS, equivalent commands are X11(,type="cairo") and either savePlot() or dev.copy2* depending on the desired format.

In this post I present two simple utility functions for opening graphics windows and saving them that operate the same for Windows and Mac OS. (You'd think someone would have done this already, but it appears not?) The basic sequence of commands is this:

openGraph(...)  # open a graphics window
plot(...)       # create graph in the window
saveGraph(...)  # save to a file in desired format

I have tested the functions on recent Windows and Mac OS machines, running from RStudio desktop. I would like to know if users encounter problems with the functions when running in a different configuration. I am told that the functions also work on Linux.

Here is an R script that defines the functions and then calls them with several different file formats. Please copy and paste the entire script into R and give it a try. Let me know how it goes.

Update January 29, 2013: The functions defined below work robustly and they are now in the program openGraphSaveGraph.R in the program repository. Several other programs have been modified to use these functions, and the others will be modified eventually.

#------------------------------------------------------------------------
# Modified 2013-Jan-22 8:55pm EST

openGraph = function( width=7 , height=7 , ... ) {
  if ( .Platform$OS.type != "windows" ) { # Mac OS, Linux
    X11( width=width , height=height , type="cairo" , ... )
  } else { # Windows OS
    windows( width=width , height=height , ... )
  }
}

saveGraph = function( file="saveGraphOutput" , type="pdf" , ... ) {
  if ( .Platform$OS.type != "windows" ) { # Mac OS, Linux
    if ( any( type == c("png","jpeg","jpg","tiff","bmp")) ) {

      sptype = type
      if ( type == "jpg" ) { sptype = "jpeg" }
      savePlot( file=paste(file,".",type,sep="") , type=sptype , ... )     
    }
    if ( type == "pdf" ) {
      dev.copy2pdf(file=paste(file,".",type,sep="") , ... )
    }
    if ( type == "eps" ) {
      dev.copy2eps(file=paste(file,".",type,sep="") , ... )
    }
  } else { # Windows OS
    file=paste(file,".",type,sep="") # force explicit extension
    savePlot( file=file , type=type , ... )
  }
}

graphics.off()
for ( typeVal in c("eps","pdf","jpg","jpeg","png") ) {
  openGraph(width=3,height=3)
  plot(1:4,c(1,3,2,4),type="o",main=bquote(typeVal==.(typeVal)))
  saveGraph( file="openGraphSaveGraphOutput", type=typeVal )
}
 

#------------------------------------------------------------------------

10 comments:

  1. There is are two tiny errors in the saveGraph function that should affect all non-Windows platforms. The third line in that function should be:
    if ( any( type == c("png","jpg","jpeg","tiff","bmp")) ) {

    (I replaced the second "jpg" with "jpeg")

    and immediately after this line, the following line should be inserted:

    sptype <- type

    With these fixes it works fine on Linux!

    ReplyDelete
  2. Actually, to be consistent with your use of "=" instead of "<-" the new line should read:

    sptype = type

    ReplyDelete
  3. Christoph:
    Thank you for catching that error. I don't know how it got into the posted code, because THE whole point of having "sptype" is to handle the difference between jpg and jpeg types on Windows and MacOS. I must have grabbed a previous version or lost something in cut and paste or maybe space aliens corrupted the file... In any case, thank you for catching the problem. I have edited the code in the post to reflect the corrections.

    ReplyDelete
  4. I also added ",..." into the called functions, so that extra arguments used in openGraph or saveGraph get passed into the called functions. I don't know why users would want to do that, but it's good to have the capability just in case.

    ReplyDelete
  5. Cool -- while you're updating that post you might as well note that it works fine on Linux. I think, at least as far as R goes, it's generally fair to assume that what works on MacOS will work on Linux.

    ReplyDelete
  6. Why not use pdf() and dev.off() ? Those have worked pretty well regardless of the platform for me.

    ReplyDelete
  7. The problem with using
    pdf(); plot(); dev.off()
    is that you cannot see the plot. I want the plot to be visible.

    ReplyDelete
  8. Has anyone had trouble with exercise 12.1 in DBDA2E? The openGraph function returns the error Error in eval(expr, envir, enclos) : could not find function "openGraph". The only thing the exercise calls for as far as sourcing is BernBeta.R.

    ReplyDelete
  9. Sorry, you'll have to source DBDA2E-utilities.R first, because that's where openGraph is defined.

    ReplyDelete