# Animating taichi diagrams

####

Youzhi Yu
University of Chicago

## Why animate?

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.

    library(ggplot2)
    library(ggtaichi)
    library(gganimate)

## How `geom_taichi()` composes with gganimate

`geom_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.)

## Animating over a third variable

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.

    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)

## Keeping glyphs round

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_tg, aes(x = category, y = state)) +
      geom_taichi(yin = Twitter, yang = Google) +
      coord_fixed() +
      theme_taichi() +
      transition_states(week, transition_length = 1, state_length = 1)

    # animate(p_fixed, width = 800, height = 600, fps = 10)

## Spin animation

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.

## Export helpers

`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.