Taking control of animations in R and demystifying them in the process
May 2, 2017
A while ago (a very long time ago some would say) I showed how I had
created my logo using R.
In that post I left on the bombshell that I would return and show you how it is
possible to add some fancy animation to it. The time to do that is now!
During this post I will go in depth with how it is possible to make very fancy
and custom animations in R. I’ll not be using either my own tweenr package or
the excellent gganimate package by David
Robinson as we want some more fine tuned control over the final look. Instead
we’ll be making parameterized functions that modify the D and i based on a
time point. Hopefully, by the end of this post I’ll have demystified the art of
animations even more than I did in my last animation post.
We’ll continue unabated from the code used to make the logo in the first place,
meaning that we have three ggplot objects at our disposal: D, i_stem, and
i_dot. We’ll focus on the animation of each in turn and then assemble it in
the end.
Animating a D
My idea for the D is to draw each line in a random order. In order to make
sure that it appears as if the lines are drawn rather than gradually appearing
(an important distinction) we need to make sure that the tip of the line is
always at maximum opacity. I want each line to take one second to draw and the
full animation to last for 6 seconds, making the first 5/6 of the animation time
the allowed slot for initiating a line drawing.
The first thing to do is to partially build the plot. If you have not tinkered
with ggplot2 you might not know that a ggplot object is converted to the plot
you see in a two-pass manner. The first pass takes care of training scales,
scaling variables, splitting data out in facets, performing statistical
transformations etc. The last pass uses all this information to actually do the
drawing. This can be used to our advantage (in the same way that gganimate uses
it) by intercepting the plot creation after the first pass and tinker with the
data. Here, our main operation will be to filter the data appropriately and
apply a transformation of the alpha values.
We subtract one from the number of edges in the last step because we want to
start the drawing at time 0. Let’s write a function that takes a time point
between 0 and 1 and only draws the edges that have appeared then:
We’re not done yet though as we still need to gradually draw the arcs rather
than have them appear out of the blue. We’ll modify the D_anim() function to
achieve this.
This actually looks like what we were aiming at. The only way to find out is to
create a lot of plots along t and assemble them into an animation
In order to create the gif yourself (it is handled automatically by knitr) you
can wrap the above in animation::saveGIF().
Animating the i
The i animation will be broken up in two separate animations - one for the dot
and one for the stem. We’ll start with the dot since this continues nicely from
the approach used with the D
Animating the dot of the i
As with the D the plan is to draw the edges progressively but this time we
won’t do it in a random fashion but instead circle around the perifery, starting
the drawing of all edges from a single node at a time. There are two layers in
this plot, both the edges and the leaf nodes, so we need to modify two data
layers as well. Another consideration is that we do not get the order of the
nodes for free - we only know their x and y coordinates and it is up to us to
translate this into a order around the circle. There are many ways to do this,
but just for the sake of self-promotion I’m going to use radial_trans() from
ggforce to translate the coordinates back to radians.
Now we got the information we need. We first decided the order in which to draw
the nodes. Then we matched the start position of the edges to the position of
the nodes (taking care of floating point problems) and used the node order to
decide the edge order.
There are some slight variations from how we did it with the D but a lot of
code is almost identical. One main difference is that we are not modifying the
alpha level this time but rather the colour gradient as the alpha level is fixed
at 0.25 to combat overplotting. Another difference is that I square the index
value as a cheap easing function making the edges shoot away from the node and
gradually loose momentum.
Once again we’ll make a quick test:
Animating the stem of the i
For the last part of the logo we’ll once again apply a bit of randomness and let
the rectangles growth forth in a random fashion. This is seemingly simple, but
there’s a catch. The plot actually consists of two layers, one of them only
containing the leafs (which draws the filled rectangles) and one containing all
nodes (drawing the borders). In order for the animation to appear natural these
two layers needs to be orchestrated.
Now that we have defined the appearance order of all elements it’s time to
decide how they shoould actually appear. In order for the borders to change
smoothly the border width should increase from zero, while the fill can fade in
by changing the opacity
This looks like it should (I think). There’re no strokes without a fill
appearing, and everything looks nice and random.
Putting it all together
As we have now succeeded in parameterizing the animation of each part of the
plot it’s fairly easy to assemble it using the same code as was used to assemble
the static version of the logo:
There you have it: A fairly complex animation of a logo, made entirely in R!