3

I want to create an animated barplot with the gganimate package. Below the barplot, I want to annotate a text box. The text within the box should change over time. The x-axis of the barplot should be moving (as specified by view_follow). However, the text box should be shown at a fixed point of the plot.

Consider the following example:

# Create example data
df <- data.frame(ordering = c(rep(1:3, 2), 3:1, rep(1:3, 2)),
                 year = factor(sort(rep(2001:2005, 3))),
                 value = round(runif(15, 0, 100)),
                 group = rep(letters[1:3], 5))

library("gganimate")
library("ggplot2")

# Create animated ggplot with coord_flip
ggp <- ggplot(df, aes(x = ordering, y = value)) +
  geom_bar(stat = "identity", aes(fill = group)) +
  transition_states(year, transition_length = 2, state_length = 0) +
  view_follow(fixed_x = TRUE) +
  coord_flip() +
  theme(axis.title.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.text.x  = element_blank(),
        axis.title.y = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text.y  = element_blank(),
        plot.margin = unit(c(1, 1, 8, 1), "cm"))

enter image description here

Question: How could I annotate a text box with changing text in the white area below this plot?

Z.Lin
  • 28,055
  • 6
  • 54
  • 94
Joachim Schork
  • 2,025
  • 3
  • 25
  • 48
  • 1
    You can't pass an aesthetic to `annotate`, so you'd have to do it with `geom_text` or `geom_label`, or update `annotate` with one of gganimate's label variables, or fall back to `animate` and do the tweening manually. Regardless, keeping it in the same place will be a pain unless you add it directly with grid. Getting it in the white part is not easy, either, depending on the approach you take. – alistaire Feb 16 '19 at 17:05
  • @alistaire Thanks for your hints. Unfortunate to hear that there doesn't seem to be a solution that is capable of providing the desired output. – Joachim Schork Feb 16 '19 at 19:52
  • I mean, there are solutions; they're just not simple. – alistaire Feb 16 '19 at 20:05
  • @alistaire Thanks for the clarification. Unfortunately, I also would need to create the graph in an automated way, since I want to create many of these graphs. A manual solution is therefore not really a sufficient option for me. – Joachim Schork Feb 16 '19 at 20:15
  • It's not manual (no clicks are involved), it's just a lot more code. Tweening beforehand, plotting each, adding a textGrob, and collecting everything into a gif. It's entirely possible—you could even parameterize it and chuck it in a function—but it's not simple. – alistaire Feb 16 '19 at 20:22
  • @alistaire OK and thanks again. I might give it a try and will update this thread in case I find a solution. – Joachim Schork Feb 16 '19 at 20:29

2 Answers2

4

Does this meet your needs?

df %>%

  # calculate the value range for each year state
  group_by(year) %>%
  mutate(max.value = max(value)) %>%
  ungroup() %>%

  # add text for each year state (I came up with some random lines for illustration)
  mutate(text = case_when(year == "2001" ~ "2001: launch of Wikipedia",
                          year == "2002" ~ "Brazil wins 2002 World Cup",
                          year == "2003" ~ "2003 saw SARS outbreak",
                          year == "2004" ~ "Olympics 2004 in Greece",
                          TRUE ~ "2005 - first YouTube upload")) %>%

  ggplot(aes(x = ordering, y = value)) +
  geom_col(aes(fill = group)) +

  # add blank layer with maximum value so that the plot's overall range
  # changes smoothly between states
  geom_blank(aes(y = max.value)) +

  # add text layer positioned in the middle, below visible range
  geom_text(aes(y = max.value / 2, label = text),
            x = 0, check_overlap = TRUE) +

  # turn off clipping to show text layer
  coord_flip(clip = "off") +

  theme(axis.title = element_blank(),
        axis.ticks = element_blank(),
        axis.text  = element_blank(),
        plot.margin = unit(c(1, 1, 8, 1), "cm")) +

  # (optional) increase state_length so that the text labels can be viewed longer
  transition_states(year, transition_length = 2, state_length = 2) +
  view_follow(fixed_x = TRUE)

(If the text string is the same for each year state, the resulting geom_text layer will stay perfectly stationary during animation.)

result

Data:

set.seed(123)
df <- data.frame(ordering = c(rep(1:3, 2), 3:1, rep(1:3, 2)),
                 year = factor(sort(rep(2001:2005, 3))),
                 value = round(runif(15, 0, 100)),
                 group = rep(letters[1:3], 5))
Z.Lin
  • 28,055
  • 6
  • 54
  • 94
1

There is an easier way. You could use the tag annotation and just use gganimates {glue}-like annotation feature. gganimate does not care if your states are numeric. It defines the state order by factor levels. This order is customisable. Thus, you can create a custom factor (!) label column and use this for your transition states and add this as a "tag" to your plot.

library(gganimate)
#> Loading required package: ggplot2

## I am choosing this example in order to show that it does not relate to alphabetical order after custom level definition.
my_labs <- setNames(rev(tail(letters,5)), 2001:2005)
 
## set the level order according to your original states 
df$label <- my_labs[df$year]
df$label <- forcats::fct_inorder(paste(df$label, df$year, sep = ": "))

p <-
  ggplot(df, aes(x = ordering, y = value)) +
  geom_col(aes(fill = group)) +
  coord_flip() +
  theme(
    plot.tag.position = "bottom",
    plot.tag = element_text(hjust = 0)
  ) +
  labs(tag = "{closest_state}")

## obviously, now use the new column as transition state
p + transition_states(label, transition_length = 2, state_length = 0) +
  view_follow(fixed_x = TRUE)

Created on 2023-03-07 with reprex v2.0.2

tjebo
  • 21,977
  • 7
  • 58
  • 94