3D networks: embedding rgl in Rmarkdown

I’m usually not a fan of 3D views of networks, because unless you’ve got a VR headset, the image needs to be moving for the 3D to work visually. Otherwise you just have 2D view with lots of edges covering each other up.

However, the Carter Butts’ sna package has included gplot3d for rendering 3D views of networks using the rgl for many years. The recent release of the htmlwidgets frameworks and Duncan Murdoch’s rglwidgets package means that we can now trivially embed 3D rgl scenes in rmarkdown documents (like this one) to display in almost all (modern) web browsers.

First, load the libraries

library(sna)
library(rgl)
library(rglwidget)

Then get the network and render the scene.

# load up the sna package
data("coleman")
# open a 3d rgl device so we can set window size and zoom
open3d(antialias=1,windowRect=c(0,0,500,500),zoom=0.5)
## glX 
##   1
# use the sna gplot3d command to render network in 3d
gplot3d(coleman,edge.col='#55555555')
# save the rgl scene
rglscene<-scene3d()
# turn off the device
rgl.close()
# render the saved rgl scene as widget in the markdown page
rglwidget(rglscene)

Drag to rotate, mouse-wheel to zoom.

presto!

rendering as a movie

This was originally prompted by a post on the SOCNET listserv about rendering 3D image as a movie. That should be possible using the gplot3d, rgl’s snapshot function and Yihui Xie’s animation package. Rendering the movie will also require having a working version of ffmpeg installed on the system.

library(animation)
# render out an animation of the rotating rgl scene
saveVideo( {
  open3d(antialias=1,windowRect=c(0,0,500,500))
  gplot3d(coleman,edge.col='#55555555')
  # gradually rotate the rgl sceen and render out frames
  # each loop is one step, we will make 100 frames
  for (i in 0:100) {
        # rotate the rgl model viewpoint
                          # rotate           # zoom out      
        rgl.viewpoint(theta=i*3.6, phi=0, zoom=0.5+i*0.005, interactive=FALSE)
        # take png snapshot of the model with an appropriate framenumber
        rgl.snapshot(paste('colemanFrame_',i,'.png',sep=""))
  }
  # turn off png device, we are done with it
  rgl.close()  
  },img.name='colemanFrame_',  # make sure the image names match up!
   use.dev=FALSE,
  other.opts = '-b 5000k',  # increase bit rate for better resolution
  video.name = 'colemanAnimation.mp4'
)
## Executing: 'ffmpeg' -y -r 1 -i colemanFrame_%d.png -b 5000k colemanAnimation.mp4

In theory, that should do it. but I seem to get an error from ffmpeg: Could find no file with path 'colemanFrame_%d.png' and index in the range 0-4, Oddly, if I run the exact command it says it is running, it works fine and generates the movie.

system("'ffmpeg' -y -r 10 -i colemanFrame_%d.png -b 5000k colemanAnimation.mp4")