Plotting networks in blender

Github link to the R code and Blender addon – always the most up to date code and documentation.

This code will take an igraph object, layout and some plotting properties and export files from R, which can then be read in the free 3d modelling software Blender. This allows you to plot attractive networks in 3d space and even animate them.

The function in R is set up very similarly to igraph.plot. You are able to  customise layout, colours, edge sizes, vertex sizes etc. The function will then export two files, one for edge and one for vertexes. These can then simply be read in using the dialogue the Blender addon adds. You can either add one file at a time or point blender to a folder. If there is more than one network in that folder, the networks will be added at a set frame interval from each other.

The code fully supports arrows and curved edges too. You can even create a purely 2d network if you want, with 2d edges, though of course it actually exists in 3d space.

The code allows you to simply use spheres, cubes, circle and squares node shapes. However it can also use any existing object in a blender scene as a node shape. Here is a network using the Blender monkey head primitive as a node shape in a network with a 3d layout:

You can also combine 2d edges with 3d node shapes. I rather like this effect:

Can also easily use a 2d picture as a node

There are a few considerations when animating a network, namely that edges and nodes that you want to animate have to exist at every keyframe, even if they are technically not present in that particular network. An example of this can be seen in the animated example above, with edges and nodes appearing and disappearing. You can see that in the blender screenshot above, the edges that are invisible in the animation are still present. In order to make this easier I have added several R functions to the script file, all designed to help ensure consistency between network. Below is an example of code for generating an animation similar to the one above, demonstrating the use of the utility functions to easily create hidden edges and nodes. This script can also be found on the net2blend github repository.

#This is an example animating nodes moving, appearing/disappearing and changing colour

#load requirements
library(igraph)
source("net2blend.R")

#generate a random network of 13 nodes
g1=erdos.renyi.game(13,0.25)
#as this method does not give nodes names, assign them randomly from the alphabet
V(g1)$name=sample(LETTERS,length(V(g1)))
#give all these nodes an attribute that they were present
V(g1)$present=T
#give edges a weight attribute
E(g1)$weight=1

#generate a second network of 26 nodes
g2=erdos.renyi.game(26,0.1)
#as this method does not give nodes names, assign them randomly from the alphabet
V(g2)$name=sample(LETTERS,length(V(g2)))
#give all these nodes an attribute that they were present
V(g2)$present=T
#give edges a weight attribute
E(g2)$weight=1

#find all nodes
allnodes=find_all_nodes(list(g1,g2))

#find all edges
alledges=find_all_edges(list(g1,g2))

#add nodes to g1 with present attribute set to false
g1=add_missing_nodes(g1,allnodes=allnodes,attrlist=list(present=F))

#If we had nodes disappearing in g2, can run the same line
g2=add_missing_nodes(g2,allnodes=allnodes,attrlist=list(present=F))

#add missing edges to g1 and g2 with a weight of 0
g1=add_missing_edges(g1,alledges=alledges,attrlist=list(weight=0))
g2=add_missing_edges(g2,alledges=alledges,attrlist=list(weight=0))

#For convenience (not required though) can also match the order of nodes
g1=permute(g1,match(V(g1)$name,sort(V(g1)$name)))
g2=permute(g1,match(V(g2)$name,sort(V(g2)$name)))

#generate edge names for convenience - makes it easier to find the same edges in different graphs
E(g1)$name=get_edge_names(g1)
E(g2)$name=get_edge_names(g2)

#generate a layout for both networks to use. 
#This will be identical, but we'll add a change in z coordinate in the second frame
l1=as.data.frame(layout_in_circle(g1))
row.names(l1)=V(g2)$name
#add a z coordinate
l1$z=0
#make a copy
l2=l1
#add z components 
l2$z[row.names(l2)%in%V(g1)[present]$name]=-0.5
l2$z[!row.names(l2)%in%V(g1)[present]$name]=0.5

#export g1
net2blend(g1,layout=l1[match(V(g1)$name,row.names(l1)),],
vertex.color="red",
edge.color="black",
edge.size=0.01*E(g1)$weight,#edges of weight 0 will be invisible
edge.dash=0,#no dashes
edge.isdashed=T,#but force the edge to prepare to be dashed
edge.curve=T,
edge.forcecurve=T,
vertex.size=0.05*V(g1)$present,#non present nodes will be invisible
netname="example1",
netname2=1)

#for this example, edges that are present in g1 will become blue in g2 while edges that are disappearing will become red
edgecol=rep("black",length(E(g2)))
edgecol[E(g2)$weight==0]="red"
edgecol[E(g2)$name%in%E(g1)[weight>0]$name&E(g2)$weight>0]="blue"

#similarly new nodes will be green while old nodes will become blue.
nodecol=rep("green",length(V(g2)))
nodecol[V(g2)$name%in%V(g1)[present]$name]="blue"

#export g2
net2blend(g2,layout=l2[match(V(g2)$name,row.names(l2)),],
vertex.color=nodecol,
edge.color=edgecol,
edge.size=0.01*E(g2)$weight,#edges of weight 0 will be invisible
edge.dash=5,#dash size
edge.isdashed=T,
edge.curve=F,
edge.forcecurve=T,
vertex.size=0.05*V(g2)$present,#non present nodes will be invisible
netname="example1",
netname2=2)#note that netname 2 is different

#Go to blender and import the export folder