58

I want to include animated rgl graphs in my .Rnw document through knitr. Here is my MWE:

\documentclass{article}

<< label = setup, include = FALSE>>=
opts_chunk$set(fig.path = 'figure/',  cache = FALSE, dev = "pdf",  fig.align = 'center', fig.show = 'hold', fig.width = 3, fig.height = 3,  echo = TRUE, warning = FALSE, message = FALSE, size = 'footnotesize', comment=NA, results='hold')

knit_hooks$set(par = function(before, options, envir){
if (before && options$fig.show!='none')
 par(mar = c(4, 4, 0.1, 0.1), cex.lab = 0.95, cex.axis = 0.9, mgp = c(2, 0.7, 0), tcl = -0.3)
}
)
knit_hooks$set(rgl = function(before, options, envir) {
  if (!before) {
    ## after a chunk has been evaluated
    if (rgl.cur() == 0) return()  # no active device
    name = paste(options$fig.path, options$label, sep = '')
    rgl.snapshot(paste(name, '.png', sep = ''), fmt = 'png')
    return(paste('\\includegraphics{', name, '}\n', sep = ''))
  }
}
)

options(replace.assign = TRUE, width = 60)
@ 
\begin{document}

<< label=packages >>=
library(car)
@
<< label=rgl1, rgl=TRUE, fig.show='animate' >>=
scatter3d(prestige ~ income + education, data=Duncan)
@

\end{document}

I'm not getting graph in my knitted documents.

Updated

I'm still unable to get it work and getting the following warning:

Warning messages:
1: In rgl.snapshot(paste(name, ".png", sep = ""), fmt = "png") :
  RGL: Pixmap save: unable to open file 'D:\A B\C D UAF\Test\knitr\rglAnimation\figure\rgl1.png' for writing
2: In rgl.snapshot(paste(name, ".png", sep = ""), fmt = "png") :
  snapshot failed
3: running command '"C:\PROGRA~2\MIKTEX~1.9\miktex\bin\x64\texi2dvi.exe" --quiet --pdf "rglAnimation.tex" --max-iterations=20 -I "C:/PROGRA~1/R/R-31~1.1/share/texmf/tex/latex" -I "C:/PROGRA~1/R/R-31~1.1/share/texmf/bibtex/bst"' had status 1 

My sessionInfo() is

R version 3.1.1 (2014-07-10)
Platform: x86_64-w64-mingw32/x64 (64-bit)

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] tools     stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] mgcv_1.8-2    nlme_3.1-117  rgl_0.93.1098 car_2.0-21    knitr_1.6.15 

loaded via a namespace (and not attached):
[1] evaluate_0.5.5  formatR_1.0     grid_3.1.1      highr_0.3      
[5] lattice_0.20-29 MASS_7.3-34     Matrix_1.1-4    nnet_7.3-8     
[9] stringr_0.6.2  

Edited

Changing fig.path = 'figure/' to fig.path = '' in opts_chunk$set compiles the document with png graphs but without any animation. How can I get animated rgl graphs with fig.path = 'figure/'.

halfer
  • 19,824
  • 17
  • 99
  • 186
MYaseen208
  • 22,666
  • 37
  • 165
  • 309
  • 3
    This example works perfectly in my environment. – kohske Aug 09 '14 at 23:08
  • the file name quoted in your `Pixmap save` error looks really weird. What's your working directory? – Ben Bolker Aug 28 '14 at 17:02
  • Thanks @BenBolker for your interest in my problem. My working directory is `D:\A B\C D UAF\Test\knitr\rglAnimation`. – MYaseen208 Aug 28 '14 at 17:46
  • 2
    I'd strongly advise you to try a working directory *without* spaces in the path name. This may not be explicitly disallowed in the documentation, and it might be fixable at the level of the package code, but it's the sort of thing that very frequently breaks R code that depends on system calls. – Ben Bolker Aug 28 '14 at 18:08
  • You are right @BenBolker. Even removing spaces from the names of working directory does not working. – MYaseen208 Aug 28 '14 at 18:22

1 Answers1

11

I've got an example based on yours that works using knitr on Windows 8.1 within RStudio 0.99.441.

This produces a 40-frame animation of the plot. It uses the built-in hook_plot_custom to include the plots which are generated manually by animating the 3d plot. The code for the animation was based on those in the help and source of play3d and movie3d. movied3d itself cannot be used because it is too inflexible in its file naming.

I've put this up on github at https://github.com/NikNakk/testAnimateRgl/ . The pdf is at https://github.com/NikNakk/testAnimateRgl/raw/master/rglKnitr.pdf

\documentclass{article}
\usepackage{animate}

<< label = setup, include = FALSE>>=
library("rgl")
library("car")
library("knitr")
knit_hooks$set(rgl = hook_plot_custom)
@ 
\begin{document}

<< label=rgl1, rgl=TRUE, fig.show='animate', fig.width=5, fig.height=5, out.width='.6\\linewidth', dev='png', fig.num = 40, interval=0.1>>=
scatter3d(prestige ~ income + education, data=Duncan)
M <- par3d("userMatrix")
par3d(windowRect = 100 + opts_current$get("dpi") *
        c(0, 0, opts_current$get("fig.width"), 
        opts_current$get("fig.height")))
spinFunc <- par3dinterp(userMatrix=list(M,
                             rotate3d(M, pi/2, 1, 0, 0),
                             rotate3d(M, pi/2, 0, 1, 0)))
for(i in 1:40) {
  par3d(spinFunc(i / 10))
  Sys.sleep(0.05)
  rgl.snapshot(fig_path(".png", number = i), fmt = "png")
}
@

\end{document}

Edit: New version

Here's another version which demonstrates the use of custom chunk options to set the parameters for the rather simpler spin3d. Note that with this version, the chunk is just a single line (the scatter3d plot). spin3d.axis is used to set the axis parameter to spin3d; spin3d.rpm is used to set the rpm parameter. The number of images and the interval between images is set using the standard fig.num and interval parameters.

\documentclass{article}
\usepackage{animate}

<< label = setup, include = FALSE>>=
  library("rgl")
library("car")
library("knitr")
hook_rgl_spin <- function(before, options, envir) {
  if (!before) {
    par3d(windowRect = 100 + options$dpi *
          c(0, 0, options$fig.width, 
            options$fig.height))
    if (!is.null(options$spin3d.axis)) {
      spin3d.axis <- options$spin3d.axis
    } else {
      spin3d.axis <- c(0, 0, 1)
    }
    if (!is.null(options$spin3d.rpm)) {
      spin3d.rpm <- options$spin3d.rpm
    } else {
      spin3d.rpm <- c(0, 0, 1)
    }
    spinFunc <- spin3d(axis = spin3d.axis, rpm = spin3d.rpm)
    for(i in 1:options$fig.num) {
      par3d(spinFunc(i * options$interval))
      Sys.sleep(0.05)
      rgl.snapshot(fig_path(".png", number = i), fmt = "png")
    }

    hook_plot_custom(before, options, envir)
  }
}
knit_hooks$set(rgl = hook_rgl_spin)
@ 
  \begin{document}

<< label=rgl1, rgl=TRUE, fig.show='animate', fig.width=5, fig.height=5, out.width='.6\\linewidth', dev='png', fig.num = 40, interval=0.1, spin3d.axis=c(0, 0, 1), spin3d.rpm = 20>>=
  scatter3d(prestige ~ income + education, data=Duncan)
@

  \end{document}
Nick Kennedy
  • 12,510
  • 2
  • 30
  • 52
  • Thanks @NickK for your excellent answer. On my machine the does not work for `fig.path = 'figure/'` so I've to change `fig.path = 'figure/'` to `fig.path = ''`. Any idea how this code will work for `fig.path = 'figure/'`. Would also appreciate if you tell me the Adobe Reader to play animation. Thanks – MYaseen208 Jul 02 '15 at 13:26
  • I had to manually create the 'figure' subfolder and it worked fine after that. Could you confirm whether you have a folder with that name already? If not, create one using whatever means you like (`dir.create("figure")` for example). I would also try manually saving a png there using `png("figure/test.png"); plot(1); dev.off()`. I used Adobe Reader XI (the current version) on Windows and Acrobat X also on Windows. It doesn't animate in Sumatra (the PDF reader within RStudio) or on any iOS pdf reader I've tried including Adobe's own. – Nick Kennedy Jul 02 '15 at 13:58
  • It might be possible to generate an animation that would work on Okular as well as Adobe Reader by passing the generated PNGs through ffmpeg to generate an .mp4 file and then using media9 to embed it. But I don't think `knitr` supports that directly so it would involve more coding. That would also in theory be supported by ezPDFreader on iOS, though I've not tested it. – Nick Kennedy Jul 02 '15 at 14:29
  • I run the first example in RStudio PDF viewer and it displays some animation for like 1 second and then I get `Error: chunk 2 (label = rgl1) Error in par3d(windowRect = 100 + opts_current$get("dpi") * c(0, 0, opts_current$get("fig.width"), : parameter "windowRect" has the wrong length Execution halted` – akhmed Jul 05 '15 at 19:48
  • @akhmed are you sure you haven't miscopied something? You could omit that line and it would still work but would be smaller than the set size. – Nick Kennedy Jul 05 '15 at 20:16
  • @NickK, tried on a different machine - same thing. Removing that line actually worked! It did create an animated plot that responds to mouse movements for several seconds. I suspect the differences must be due to differences in library versions/LaTeX versions? – akhmed Jul 06 '15 at 00:52
  • @akhmed maybe. It should work though - that line is copied from the standard rgl hook. – Nick Kennedy Jul 06 '15 at 01:07
  • @NickK, Is it possible to have pdf files rather than png files? Thanks – MYaseen208 Jul 09 '15 at 04:30
  • @MYaseen208 that is what it does! The PNGs are temporary and are used as input by `knitr` and LaTeX. If you mean to use PDFs so as to have a vector animation, then no. The snapshot function for rgl plots only works for PNGs. – Nick Kennedy Jul 09 '15 at 06:25