1

I am currently trying to alias the R function png with CairoPNG to generate png files. I am coming from a sys admin perspective on R - meaning I did not write any of the R code, and I am not in a position to change any of the R code. I am running it in pipelines in an elastic HPC environment. Because of the nature of the environment, I would have to install X11, cairo, etc on every single execute node at start time (which would add 2-3 minutes of arbitrary time to every job).

I have been playing around and have installed the R package Cairo which can generate png's without X11 forwarding, which is exactly what I need. If I try to use png by default:

cars <- c(1,3,5)
png("cars.png")
Error in png("cars.png") : X11 is not available

I realized I can circumvent this by assigning CairoPNG to png in an active session:

cars <- c(1,3,5)
png <- CairoPNG
png("cars.png")
plot(cars)

produces a .png file called cars.png. So I am looking to do this same thing from the .Rprofile where every R script that relies on png would actually be using CairoPNG under the hood.

In my .Rprofile, I have added:

require("Cairo")
png <- CairoPNG

When I start a new interactive R session via the command line, I can see Cairo being used, but the assignment of png <- CairoPNG is not working:

Loading required package: Cairo
> png("cars.png")
Error in png("cars.png") : X11 is not available

Any help would be greatly appreciated!

d_kennetz
  • 5,219
  • 5
  • 21
  • 44
  • I think `.Rprofile` is ran before the default packages are loaded. So your `png` version gets masked when `grDevices` is loaded. – Axeman Mar 17 '20 at 18:54
  • Thanks @G.Grothendieck, that appears to be an alternative approach which might be useful! – d_kennetz Mar 17 '20 at 18:56
  • @Axeman thanks, so you are saying that any changes I made to a default function could not be carried forward from the Rprofile because they will be loaded by the script after? – d_kennetz Mar 17 '20 at 18:59
  • @G.Grothendieck Excellent, that did exactly what I asked for in the question! The code all makes sense, I am just not familiar with R, but the functions are all pretty self-explanatory :D. – d_kennetz Mar 17 '20 at 20:02
  • 1
    Have transferred my comment to an answer. – G. Grothendieck Mar 17 '20 at 20:09
  • Minor, but important: Don't get into the habit of using `require("Cairo")` - always use `library("Cairo")`. The latter will give an error if the 'Cairo' package is not available whereas the former will silently ignore such errors. – HenrikB Mar 17 '20 at 20:16
  • That is good to know @HenrikB, I actually did that because the people who wrote the code I'm trying to run imported packages that way... Perhaps I should drop the same piece of advice to them :D. – d_kennetz Mar 17 '20 at 20:17
  • 1
    Yes, please do. It's an unfortunately misleading name that caused this misusage to spread like ... you know. We need to stop it. – HenrikB Mar 17 '20 at 20:20

1 Answers1

2

Add a message statement so when R starts we can verify that the .Rprofile did, in fact, run. Also use library instead of require because library will give an error right at that point if it fails making it easier to debug. Then instead of putting png in the global environment insert it into the grDevices namespace. To do that it must be unlocked first.

# This code goes in .Rprofile file
message("Hello")
library("Cairo")
unlockBinding("png", asNamespace("grDevices"))
utils::assignInNamespace("png", CairoPNG, "grDevices")

For a different approach check out:

How to run R on a server without X11, and avoid broken dependencies

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • May I suggest to suggest using `message()` instead of `cat()`. The latter outputs to stdout and that will get included in Rmarkdown etc., whereas `message()` outputs to stderr which is not captured by dynamic reports. This is something that users might struggle with when they try out Rmarkdown months from now. – HenrikB Mar 17 '20 at 20:18
  • 1
    To clarify the idea behind this solution: If a package uses `png()` internally, it will be wired to `grDevices::png()`, so it's not enough to add your local one with the same name - you need to inject your hack into the `grDevices` package. – HenrikB Mar 17 '20 at 20:23