John K. Kruschke
John K. Kruschke

Reputation: 535

How can I get Rmarkdown to include a plot from a window opened by a sourced script?

I'm using Rmarkdown in RStudio and including plots that are opened in new graphics windows. If the window is opened directly in a code chunk, then the plot is included in the processed document. But if the window is opened by a separate script that is sourced, then the plot appears during Knitr processing but is not included in the document. Here is a complete minimal example of an .Rmd script to demonstrate:

---
title: "Rmarkdown graph inclusion"
---

# Make a simple plot:

```{r}
plot(0,0,main="Attempt 1")
```

Result of above: Plot is displayed during processing and is included in generated document.


# Make the plot in a separate graphics window:

```{r}
windows() # open new graphics window; x11() on Linux, MacOS
plot(0,0,main="Attempt 2")
```

Result of above: Plot is displayed during processing and is included in generated document.

# Make the plot in a separate graphics window called from another script:

```{r}
writeLines( "windows() ; plot(0,0,main='From File')" ,
            con="openWindowScript.R" )
source("openWindowScript.R")
```

Result of above: Plot **is** displayed during Knitr processing but is **NOT** included in the generated document. *Why not?*

I did search stackoverflow and elsewhere for an answer but didn't find one. Thanks in advance for answers or pointers!

Upvotes: 4

Views: 3227

Answers (2)

CL.
CL.

Reputation: 14957

The code suffers from two different issues. One is the code in openWindowScript.R, the other is how this file is included into the main document.

The code in openWindowScript.R does not even produce a visible plot when used directly as chunk code:

```{r}
windows(); plot(0,0,main='From File')
```

I think this is due to the difference between top-level expressions and other code (mentioned for example here), but I'm not sure about the details. What does produce a visible plot is the following code:

```{r}
windows()
plot(0,0,main='From File')
```

So with this code in an external file, how could we include it in the document? Not by source-ing the external file, presumably because then the plotting command again is not a top-level expression. Although Mark Peterson already provided a nice workaround, I would like to suggest a more knitr-idiomatic solution: Inject the code into a chunk, using the code option:

```{r}
writeLines( "windows() \n plot(0,0,main='From File')" ,
            con="openWindowScript.R" )
```

```{r, code = readLines("openWindowScript.R")}
```

Upvotes: 1

Mark Peterson
Mark Peterson

Reputation: 9570

If you add dev.print() after the source call, it should print the current device (which should, in your case) be the one called by source. This will then be captured by knit and included in the document. So, the chunk should look like this:

```{r}
writeLines( "windows() ; plot(0,0,main='From File')" ,
            con="openWindowScript.R" )
source("openWindowScript.R")

dev.print()
```

I tested this on Linux, which uses X11 for both opening a device and printing it, but the documentation appears to imply that it should work the same on Windows (as long as the Windows specific version of dev.print is correctly installed, which it should be by default).

If the dev.print is causing issues when run interactively (either just a nuisance or causing a crash), you can block it from running outside of a knit document by checking the name of the file being knit. This returns NULL when run interactively, so can be used as a condition in if to block execution outside of knitting.

Using the example from the comment about this error, the code chunk becomes:

```{r echo=1:2}
writeLines( "windows() ; plot(0,0,main='Ta-Da!')" ,
            con="theScript.R" )
source("theScript.R")

if(!is.null(knitr::current_input())){
  deviceInfo <- dev.print()  
}
```

A separate approach is to over-write the behavior of windows() (and/or x11). At the top of your Rmd document, add

x11 <- windows <- function(...){invisible(NULL)}

Which should catch all of the calls to windows or x11 and essentially ignore them (the ... ensures that you shouldn't get "unused argument" errors). If you are using those calls to set size and/or aspect ratio, you would need to use fig.width or fig.height instead. This may break other things where you actually want the behavior of x11/windows, but using grDevices::x11 (or similar for windows) would get you the right function. So, if you are in a pinch, and willing to forgo the reason you used windows in the first place, this should work.

Upvotes: 2

Related Questions