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 30 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.
p <- ggplot(states_tg, 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)
# Render to a GIF (requires the gifski package)
# animate(p, renderer = gifski_renderer())
# anim_save("taichi.gif", p)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:
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”
— 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)
# animate(p_spin, width = 300, height = 300, fps = 12)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). For a grow-in
reveal instead, keep a positive transition_length and add
enter_grow() to the plot.
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.