# gganimate with bitmap fonts

## Introduction

I finally got the time to play with the gganimate package by Thomas Lin Pederson.

The API is really well designed and Thomas has deconstructed the grammar of animation into a clean/orthogonal set of functions.

I put together the little animation shown below, and this is a short guide on how I got there.

## Bitmap fonts

In a bitmap font, a character is defined by a binary matrix - where the matrix is 1 the underlying pixel is turned on, otherwise it is turned off.

An example matrix of an 8x8 binary matrix to define the letter a is shown below:

     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,]    0    0    0    0    0    0    0    0
[2,]    0    0    0    0    0    0    0    0
[3,]    0    0    1    1    1    1    0    0
[4,]    0    0    0    0    0    1    1    0
[5,]    0    0    1    1    1    1    1    0
[6,]    0    1    1    0    0    1    1    0
[7,]    0    0    1    1    1    1    1    0
[8,]    0    0    0    0    0    0    0    0

This matrix can be easily plotted with the raster package

withr::with_package('raster', {
plot(raster(char_matrix$a), col=c(1,0), legend=FALSE, ann=FALSE, axes=FALSE) }) Rather than individually creating bitmap font matrices, I’m actually reading in bitmap font files from disk. I haven’t packaged that code up yet, but hopefully will do so soon. For this post I’ll just use some manually created character data stored in char_df. ## Rendering a bitmap font character with ggplot2 To work with ggplot2, the matrix for an 8x8 character is turned into a data.frame. The data.frame is the x and y co-ordinates of all the locations of 1 in the bitmap matrix. Here is a data.frame for the same a, now ready for ggplot2.  x y 1 2 5 2 3 5 3 4 5 4 5 5 5 5 4 6 6 4 7 2 3 8 3 3 9 4 3 10 5 3 11 6 3 12 1 2 13 2 2 14 5 2 15 6 2 16 2 1 17 3 1 18 4 1 19 5 1 20 6 1 This data.frame is plotted using ggplot2::geom_tile() ggplot(char_df$a, aes(x, y)) +
geom_tile() +
coord_equal() +
theme_minimal()

The tile size is set a bit smaller than 1 to get the retro pixel look:

ggplot(char_df$a, aes(x, y)) + geom_tile(width=0.9, height=0.9) + coord_equal() + theme_minimal() ## Preparing a data.frame for gganimate The data.frame for gganimate is going to consist of 3 characters (a, b, c), displayed one-at-a-time. • Create a data.frame for each bitmap character • Give each character an idx which will let gganimate control when it is displayed • Stack them all into a single data.frame with dplyr::bind_rows() plot_df <- dplyr::bind_rows( char_df$a %>% mutate(idx = 1),
char_df$b %>% mutate(idx = 2), char_df$c %>% mutate(idx = 3)
)

Plotting without gganimate results in the 3 letters being superimposed. Not really very useful, but a stepping stone to the actual animation.

p <- ggplot(plot_df, aes(x, y)) +
geom_tile(width=0.9, height=0.9) +
coord_equal() +
theme_minimal()

p

Below I facet by idx to separate out each character in space. gganimate will use the idx column to separate the characters in time using transition_states().

p +
facet_wrap(~idx) +
theme_bw() 

## Plotting with gganimate - facetting in time!

gganimate takes a standard ggplot and separates out the individual states (i.e. the idx variable) by rendering them at different times.

By analogy:

• ggplot2::facet_wrap() renders the data at different locations in space dependent upon the facetting variable
• gganimate::transition_states() renders the data at different times dependent upon the states variable.
panim <- p +
transition_states(
states            = idx, # variable in data
transition_length = 1,   # all states display for 1 time unit
state_length      = 1    # all transitions take 1 time unit
) +
enter_fade() +             # How new blocks appear
exit_fade() +              # How blocks disappear
ease_aes('sine-in-out')    # Tweening movement

panim

## Controlling the animation length and image size

By default, the animation is scaled into 100 frames of PNG output at 10 frames-per-second. These png files are then rendered to an animated gif using gifski.

By default the individual images are rendered using R’s built-in png() which has a default output size of 480x480 pixels.

To render the gif differently (e.g. shorter duration, faster playback, smaller image size), you can call animate() directly on the plot object

animate(panim, fps=20, nframes=50, width=200, height=200)

## To Do

• Package up the code for parsing bitmap font files