Youzhi Yu
University of Chicago
Every animation on this page is really rendered when the vignette is built. Before 0.3.0 the
animate()calls here were commented out for CI, which is how a bug that collapsed every transition to a single frame went unnoticed through two releases. SeeNEWS.md.
A taichi (yin-yang) symbol is cyclical, so motion is
unusually on-brand for this geom. Instead of forcing a third variable
(e.g. week) onto the x-axis and shrinking every glyph, we
can turn it into an animation frame. Each frame is then
a clean category x state grid of large,
readable taichi.
This vignette shows how to combine geom_taichi() with gganimate. gganimate is a
Suggested dependency; install it with
install.packages("gganimate") if you have not already.
geom_taichi() composes with gganimategeom_taichi() returns a pair of layers separated by
ggnewscale::new_scale_fill(), so each fish keeps its own
fill scale and legend. gganimate works at the layer level: it
splits every layer’s data by the transition variable and builds one
frame per state. Because the two fish layers are ordinary
ggplot2 layers, gganimate treats them independently and the
two fill scales continue to apply frame-by-frame. In short: the
ggnewscale layer stack composes cleanly with gganimate, so no
special wrapper is needed. (This is not just theory: every recipe below
has been rendered frame-by-frame against gganimate 1.0.11, with both
legends and the per-fish fills intact in every frame.)
Use transition_states() when the frame variable is
discrete (e.g. week number), or transition_time() when it
is a continuous time. Both drive the underlying fish layers. The bundled
states_tg data is a perfect showcase: instead of squeezing
its 31 weeks onto the x-axis, keep a category x
state grid of large glyphs and let the weeks play out as
frames. Because the fills are continuous, tweenr
interpolates the values smoothly between consecutive weeks.
states_small <- subset(states_tg, week <= 8)
p <- ggplot(states_small, aes(x = category, y = state)) +
geom_taichi(yin = Twitter, yang = Google) +
theme_taichi() +
labs(title = "Week {closest_state}") +
transition_states(week, transition_length = 1, state_length = 1)
show_animation(p, nframes = 24)Taichi symbols are always drawn round (they are sized in square
units, like grid::circleGrob()), but
coord_fixed() keeps the cells square too, so the
glyphs fill them evenly. Match the animation dimensions to the grid:
p_fixed <- ggplot(states_small, aes(x = category, y = state)) +
geom_taichi(yin = Twitter, yang = Google) +
coord_fixed() +
theme_taichi() +
transition_states(week, transition_length = 1, state_length = 1)
show_animation(p_fixed, nframes = 24, width = 560, height = 420)With the angle argument (see ?geom_taichi)
you can rotate each glyph. Mapping angle to an expression
of the frame variable and animating produces the iconic “turning
taichi”, and with eyes = TRUE each eye rides around in its
own fish’s head:
spin <- data.frame(
x = 1, y = 1, yin = 5, yang = 5,
frame = 1:36
)
p_spin <- ggplot(spin, aes(x, y)) +
geom_taichi(yin = yin, yang = yang, angle = frame * 10,
eyes = TRUE) +
coord_fixed() +
theme_taichi() +
transition_states(frame, transition_length = 0, state_length = 1)
show_animation(p_spin, nframes = 36, fps = 12, width = 300, height = 300)Note the state_length = 1 with
transition_length = 0: each frame is a state, so
the rotation advances in crisp steps (at least one of the two lengths
must be positive, or gganimate cannot allocate frames).
A cell that appears or disappears between states pops in and out
whole. enter_grow() and exit_shrink() have no
visible effect, because they scale a size the glyph does not have, and
enter_fade() and enter_recolour() stop with an
error on any plot that uses a ggnewscale::new_scale_fill()
break, which every geom_taichi() plot does.
enter_drift() does move an entering glyph in.
gganimate::anim_save() writes the animation to a file.
For MP4 output use ffmpeg_renderer() (requires a system
ffmpeg); for GIF use gifski_renderer()
(requires the gifski package). Aim for 10–15 fps and enough
frames that the motion is smooth; coord_fixed() keeps every
glyph round in the rendered video.