Interactive Visualization and Animated Plots
April 27, 2026
Interactive graphics are useful when viewers need to explore details that would clutter a static chart.
plotly::ggplotly()Best when you already have a regular ggplot and want a quick interactive version.
plotly::ggplotly()plotly can create interactive figures directly.plotly::ggplotly().ggplotly() converts many existing ggplot objects into interactive HTML widgets.set.seed(310)
dat <- data.frame(
group = rep(c("A", "B"), each = 10),
xvar = 1:20 + rnorm(20, sd = 3),
yvar = 1:20 + rnorm(20, sd = 3)
)
p_small <- ggplot(dat, aes(x = xvar, y = yvar, color = group)) +
geom_point(size = 3, alpha = 0.75) +
labs(x = "X variable", y = "Y variable", color = "Group")
p_smallseniority measures how long a member has served in Congress.les measures legislative effectiveness.party indicates whether the member is a Democrat or Republican.ggiraphgeom_*_interactive()girafe()ggiraph adds interactivity through special ggplot geoms.
tooltip: text displayed when the viewer hovers over an element.data_id: an ID used for hover and click behavior.onclick: JavaScript action when an element is clicked.To create a ggiraph plot, replace a regular geom with an interactive version.
geom_point() becomes geom_point_interactive().geom_col() becomes geom_col_interactive().geom_sf() becomes geom_sf_interactive().girafe(ggobj = p) to display the result.Animated plots are useful when the story involves change over time, stages, or categories.
gganimategganimate starts with a regular ggplot and adds a transition.
gganimateConsider the relationship between cylinders and miles per gallon in mtcars.
transition_states()transition_states() for categorical variables.transition_length controls how long it takes to move between states.state_length controls how long the animation pauses on each state.Here we summarize the number of Democratic and Republican House members by year.
transition_layers()This plot compares seniority and bills passed, with separate layers for each party.
cces_115 <- cces |>
filter(congress == 115)
p_layers <- ggplot() +
geom_jitter(
data = filter(cces_115, party == "Democrat"),
aes(x = seniority, y = all_pass, color = party),
alpha = 0.65
) +
geom_jitter(
data = filter(cces_115, party == "Republican"),
aes(x = seniority, y = all_pass, color = party),
alpha = 0.65
) +
geom_smooth(
data = filter(cces_115, party == "Democrat"),
aes(x = seniority, y = all_pass, color = party),
se = FALSE
) +
geom_smooth(
data = filter(cces_115, party == "Republican"),
aes(x = seniority, y = all_pass, color = party),
se = FALSE
) +
scale_color_manual(values = party_colors) +
labs(
x = "Seniority",
y = "Bills passed",
color = "Party"
)
p_layersenter_*() and exit_*() control how elements appear and disappear.
shadow_*() keeps some information from previous or future frames.
shadow_wake() shows preceding frames with gradual fading.shadow_mark() keeps previous frames visible.shadow_trail() leaves a trail of earlier positions.shadow_null() removes shadows.p_seats_time <- cong_dat |>
ggplot(aes(x = year, y = seats,
fill = party)) +
geom_col() +
geom_hline(yintercept = 217) +
scale_fill_manual(
values = party_colors) +
labs(
x = "Year", y = "Number of seats",
color = "Party"
) +
theme_minimal()
p_seats_time +
transition_time(year) +
shadow_wake(wake_length = .4) +
labs(title = "Year: {frame_time}")p_seats_time <- cong_dat |>
ggplot(aes(x = year, y = seats,
color = party)) +
geom_line(linewidth = 1.2) + geom_point(size = 3) +
geom_hline(yintercept = 217) +
scale_color_manual(
values = party_colors) +
labs(
x = "Year", y = "Number of seats",
color = "Party"
) +
theme_minimal()
p_seats_time +
transition_time(year) +
shadow_wake(wake_length = .4) +
labs(title = "Year: {frame_time}")p_gap <- gapminder |>
ggplot(aes(x = gdpPercap, y = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.75) +
scale_x_log10(labels = scales::dollar_format()) +
scale_color_tableau() +
guides(size = "none") +
labs(
x = "GDP per capita",
y = "Life expectancy",
color = "Continent"
) +
theme_minimal() +
theme(legend.position = "top")
p_gaptransition_reveal()transition_reveal() is useful for showing a line or path over time.
view_follow()view_follow() lets the viewing window adjust as the animation moves.
anim_us <- gapminder |>
filter(country == "United States") |>
ggplot(aes(x = year, y = pop/10^6)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2.5) +
labs(
x = "Year",
y = "Population (in million)",
title = "Year: {frame_along}"
) +
theme_minimal(base_size = 14) +
transition_reveal(year) +
view_follow()
anim_usanimate() and anim_save()Use animate() when you want more control over output size, speed, and duration.
rewind: Controls what happens at the end of the sequence.
FALSE: Jumps back to the first frame and repeats from the beginningTRUE: After reaching the last frame itβll play the animation in reverse back to the start