4

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!

Yihui Xie
  • 28,913
  • 23
  • 193
  • 419
John K. Kruschke
  • 413
  • 3
  • 13
  • P.S. Like Thorndike's Cat, I've tried random variations of R functions and Knitr chunk options but without success. Setting `local=TRUE` in `source()` does not work. Various settings of `fig.show` and `fig.keep` in the chunk options also have no effect, at least not for me. – John K. Kruschke Dec 12 '16 at 12:13
  • Are you just looking for *any* way to include a plot that is produced in `openWindowScript.R` (no matter if `source` is used or not) or is sourcing the external file a specific requirement? – CL. Dec 14 '16 at 14:18
  • There are pre-existing R scripts that use `windows` (or `x11`) to create plots. I want to run those pre-existing R scripts from a code chunk in Rmarkdown, by `source`ing them because that's how users run the scripts. I don't want to alter the pre-existing R scripts. (I simply want to demonstrate in Rmarkdown the scripts that accompany my textbook. Those scripts `source` other scripts that make plots in new graphics windows.) – John K. Kruschke Dec 14 '16 at 14:52
  • This may be as good of a place as any to ask: why do you need to use `windows`/`x11` in the first place? The `source`ing works fine if you omit that step. One of the first things I did with each of your scripts when I worked through them was to remove those steps (to keep the plot windows where I wanted them, particularly as I use RStudio). Love the book (and examples), but still see the `windows` step as a bit of an idiosyncrasy. – Mark Peterson Dec 14 '16 at 17:44
  • @Mark: The separate graphics windows are primarily for the cases in which the graphs are only legible with sufficient size and aspect ratio. Then, since doing it for those cases, I just try to be consistent with other cases. (Thanks for your answer below; will check it out soon!) – John K. Kruschke Dec 14 '16 at 17:58

2 Answers2

2

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.

Mark Peterson
  • 9,370
  • 2
  • 25
  • 48
  • ```{r echo=1:2} \\ writeLines( "windows() ; plot(0,0,main='Ta-Da!')" , con="theScript.R" ) \\ source("theScript.R") \\ # The following `dev.print` is needed only for Rmarkdown to display the plot in the document. Must assign the return value (i.e., name and number of device) to a variable so it is not displayed in the Rmarkdown document. Use `echo=1:N`in r code chunk options, where N is number of lines above this. \\ deviceInfo = dev.print() \\ ``` \\ (\\ above means newline) Result of above: Works when Knitr-ed, but *crashes RStudio* when R code is run by itself. – John K. Kruschke Dec 14 '16 at 19:38
  • Does it crash inside RStudio when not saving the result to `deviceInfo`? (If so, setting the chunk option `results='hide'` could be a workaround.) The code, both in my example and yours, does not cause RStudio to crash for me (on Ubuntu though, so may not be comparable). I hate to try the IT cop-out, but did you try closing and re-opening RStudio (to see if it is some other conflict)? In any case, I edited to add a check of whether or not you are knitting (and only run `dev.print` if you are). – Mark Peterson Dec 14 '16 at 19:55
  • Very nice! But while you're here, one more glitch: When the sourced script opens multiple windows, only the final one is included in the document. For example, \\ ```{r echo=1:2} \\ writeLines( "windows() ; plot(0,0,main='Ta-Da ONE!') ; windows() ; plot(0,0,main='Ta-Da TWO!')" , con="theScript2.R" ) \\ source("theScript2.R") \\ if(!is.null(knitr::current_input())){ deviceInfo <- dev.print() } \\ ``` \\ Ideas? – John K. Kruschke Dec 14 '16 at 20:17
  • It looks like `dev.print` (and `dev.copy`) only work on the current device. So, I don't see a path using those. I did add an approach that just overwrites the behavior of `windows`, which should solve the issue through your whole file (and avoid the unnecessary brief popup graphics devices you get while processing the Rmd file). – Mark Peterson Dec 14 '16 at 20:48
  • Awesome! Thank you! (System won't let me award the bounty yet; will do when it's allowed.) – John K. Kruschke Dec 14 '16 at 21:47
1

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")}
```
Community
  • 1
  • 1
CL.
  • 14,577
  • 5
  • 46
  • 73
  • Thank you for this interesting answer. The link to the discussion of top-level expressions was revealing, and helps to explain why dev.print() works. I also like the idea of code=readLines() as a chunk option, but explicitly `source`ing the code is part of the script I want to illustrate in Rmarkdown. Thank you again! – John K. Kruschke Dec 14 '16 at 23:33
  • Just to be clear, on my system the `source`d code with a newline instead of semicolon still does not generate a visible plot in the knitr output -- does it for yours (without the `code` option or my workaround)? I'm also not sure that this is a top-level issue as the plot *is* printed when run interactively (though, such top-level issues often are very important). I had forgotten about `code =` though, so a very good path forward if the workarounds fail. Combining it with `echo = FALSE` and as a separate chunk from where you show the `source` command (with `eval = FALSE`) could work. – Mark Peterson Dec 15 '16 at 13:32
  • @MarkPeterson `"Just to be clear, on my system the sourced code with a newline instead of semicolon still does not generate a visible plot in the knitr output -- does it for yours (without the code option or my workaround)?"` No, it doesn't. – CL. Dec 15 '16 at 14:38