Prof James P. Curley
6th April 2016
static img → compiled video
e.g. png → mp4 (using ffmpeg)
gganimate (for ggplot based packages)
ndtv & networkDynamic
There are pros and several cons for all methods. It can be very hard to get the look you want.
Produce and set initial layout of network nodes.
Iterate through each time interval of the network, remaking the network using the initial layout of the nodes (some jitter ok).
Optional: Hide nodes/edges that are not active yet or not active in a time bin.
Generate static images for each time unit (or several images per time unit for smoother transitions)
Compile them into a movie using ffmpeg or something similar.
# Set Output for each frame
png(file="output_%03d.png", width=800,height=450)
#Time loop starts
for(time in seq(1, total_time,dt)){ #dt is the defined interval between successive plots
gt <- delete_edges(g,which(E(g)$time > time)) #remove absent edges
layout.new <- layout_with_fr(gt,coords=layout.old,niter=10,start.temp=0.05,grid="nogrid") #jitter layout
plot(gt,layout=layout.new,vertex.label="",vertex.size=1+2*log(degree(gt)),
vertex.frame.color=V(g)$color,edge.width=1.5,asp=9/16,margin=-0.15) #make plot
layout.old <- layout.new #keep layout for next image
}
This code is based on an example by Esteban Moro. My version is here.
The video examples here are for demo purposes - they aren't optimized!
I chose the level of video quality to fit into a talk. There are several things that can be done to improve video quality including:
Can also do this for ggnetwork output:
Flatten igraph/network object to a dataframe using ggnetwork
Identify time point when nodes first appear
Make list of dataframes for each time, adding in rows with only nodes that have appeared by this time-point (use “NA” for all variables except x/y/vertex.name)
Add in other nodes to df but you NA for x/y (to ensure color consistency)
5.. Use ggsave
to store all images.
df.sp <- split(df, df$time)
for(i in 1:max(df$time)){
#to ensure color consistency among groups
tmp<-rbind(df.sp[[i]],
data.frame(x=NA,y=NA,Group=1:max(df$Group),
na.x=NA,vertex.names=NA,xend=NA,yend=NA,na.y=NA,time=NA))
pp <- ggplot(tmp, aes(x = x, y = y, xend = xend, yend = yend)) +
geom_edges(color = "black") +
geom_nodes(aes(color = factor(Group)), size = 8) +
geom_nodetext(aes(label = vertex.names),color="black", fontface = "bold") +
theme_blank() +
xlim(0,1)+ylim(0,1) +
theme(legend.position="none") +
guides(color=guide_legend(keyheight=0.3,default.unit="inch",override.aes = list(size=6)))
ggsave(pp,filename=paste0("output_",i,".png", sep=""))
}
This example can be found here.
This output is too fast - I did not put images between changes in nodes yet to make it smoother - will update
The same plot as above can be made directly in R using David Robinson's gganimate
package which wraps the animation
package to animate ggplot2 plots. My example here.
It also requires ffmpeg being installed.
There are various options that can be used to change the quality of output (it can output to other format types e.g. gif)
Similarly we can use gganimate
with ggraph
- though I have yet to fully explore this option.
Here is one example.
this visualizes edges over time but fades older ones out using a delay.
the delay effect is achieved by adjusting the edge weight by 'delay' time.
edge color is achieved using the gEdges
function.
We use frame
to determine the 'timebin' to compile images by, and set edge attributes using gEdges
and geom_edge_link0
by time using the 'delay' variable.
geom_node_point(aes(size = degree, colour = factor(group))) +
geom_edge_link0(aes(frame = timebins, alpha = delay, width = delay, colour = factor(node1.group)), data = gEdges(nodePar = 'group')) +
Network Dynamic Temporal Visualizations - used a lot in dynamic network model analysis
https://cran.r-project.org/web/packages/ndtv/vignettes/ndtv.pdf
author: Skye Bender-deMoll
There is a lot in this package ! The following examples are based on the vignette above and some examples in this tutorial by Katherine Ognyanova.
My code is available here.
# make a random network graph and add some V/E attributes
library(intergraph)
library(network)
library(randomNames)
set.seed(5)
N=30
genders <- sample(c("Female", "Male"), N,T)
df <- data.frame(id = randomNames(N,gender=genders, name.order = "first.last", name.sep = " "),
sex=genders)
g <- sample_forestfire(N, fw.prob=0.3,bw.factor=.9,directed=F)
net<-intergraph::asNetwork(g)
net %v% "col" <- c("green", "gold", "blue", "red", "pink")[edge.betweenness.community(g)$membership]
net %v% "sex" <- genders
net %v% 'id'<- as.character(df$id)
net %v% "sizevar" <- sample(5:15,vcount(g),T)
net %e% "type" <- sample(LETTERS[1:4],ecount(g),T)
net %e% "weight" <- igraph::degree(g)
library(ndtv)
render.d3movie(net, usearrows = F, displaylabels = F, bg="#111111",
vertex.border="#ffffff", vertex.col = net %v% "col",
vertex.cex = (net %v% "sizevar")/8,
edge.lwd = (net %e% "weight")/3, edge.col = '#55555599',
vertex.tooltip = paste("<b>Name:</b>", (net %v% 'id') , "<br>",
"<b>Gender:</b>", (net %v% 'sex')),
edge.tooltip = paste("<b>Edge type:</b>", (net %e% 'type'), "<br>",
"<b>Edge weight:</b>", (net %e% "weight" ) ),
launchBrowser=T, filename="Network.html" )
net
# Network attributes:
# vertices = 30
# directed = FALSE
# hyper = FALSE
# loops = FALSE
# multiple = FALSE
# bipartite = FALSE
# name = Forest fire model
# fw.prob = 0.3
# bw.factor = 0.9
# ambs = 1
# total edges= 45
# missing edges= 0
# non-missing edges= 45
#
# Vertex attribute names:
# col id sex sizevar vertex.names
#
# Edge attribute names:
# type weight
#
# NetworkDynamic properties:
# distinct change times: 46
# maximal time range: 0 until 45
#
# Dynamic (TEA) attributes:
# Vertex TEAs: animation.x.active
# animation.y.active
#
# Includes optional net.obs.period attribute:
# Network observation period info:
# Number of observation spells: 1
# Maximal time range observed: 0 until 45
# Temporal mode: continuous
# Time unit: unknown
# Suggested time increment: NA
#
# Network attributes:
# vertices = 30
# directed = FALSE
# hyper = FALSE
# loops = FALSE
# multiple = FALSE
# bipartite = FALSE
# name = Forest fire model
# fw.prob = 0.3
# bw.factor = 0.9
# ambs = 1
# net.obs.period: (not shown)
# ...
# ...
# slice.par:
# Length Class Mode
# start 1 -none- numeric
# end 1 -none- numeric
# interval 1 -none- numeric
# aggregate.dur 1 -none- numeric
# rule 1 -none- character
# total edges= 45
# missing edges= 0
# non-missing edges= 45
#
# Vertex attribute names:
# active animation.x.active animation.y.active col id sex sizevar vertex.names
#
# Edge attribute names:
# active type weight
# set up NetworkDynamic df
vs <- data.frame(onset=0, terminus=45, vertex.id=1:30)
es <- data.frame(onset=1:45, terminus=45,
head=as.matrix(net, matrix.type="edgelist")[,1],
tail=as.matrix(net, matrix.type="edgelist")[,2])
net.dyn <- networkDynamic(base.net=net, edge.spells=es, vertex.spells=vs)
# Pre-compute animation coordinates
compute.animation(net.dyn, animation.mode = "kamadakawai",
slice.par=list(start=0, end=45, interval=2,
aggregate.dur=1, rule='any'))
# Make movie
render.d3movie(net.dyn, usearrows = F, displaylabels = F, label=net %v% "id",
bg="#111111",
vertex.col = net %v% "col",
vertex.cex = function(slice){ degree(slice)/2.5 },
edge.lwd = (net %e% "weight")/3, edge.col = '#55555599',
vertex.tooltip = paste("<b>Name:</b>", (net %v% 'id') , "<br>",
"<b>Gender:</b>", (net %v% 'sex')),
edge.tooltip = paste("<b>Edge type:</b>", (net %e% 'type'), "<br>",
"<b>Edge weight:</b>", (net %e% "weight" ) ),
launchBrowser=F, filename="NetworkDynamic1.html",
render.par=list(tween.frames = 15, show.time = F),
script.type='remoteSrc')
Kenton Russell
Bryan Lewis
Bob Rudis
Thomas Pedersen
Bennett Zhang