Interactive graphics

SISBID 2025
https://github.com/dicook/SISBID

Some interactive graphics packages

  • crosstalk: that’s what shiny is based on - we will look into shiny later
  • plotly: interactive javascript graphics (maintained Carson Sievert)
  • leaflet: (RStudio) allows to make interactive maps. Has been picking up users and has developed a stable user base.
  • ggvis: both static and interactive graphics, work on it has stalled … (Wickham)
  • animint2: interactive, linked graphics with ggplot2 syntax (Toby Hocking)
  • rCharts, rbokeh, gridSVG, epivizr, cranvas previous approaches to packages with interactive graphics
  • see also https://r-graph-gallery.com/interactive-charts.html for additional packages (more specific)

Interactive Graphics

plotly

The plotly package in R has two interfaces:

  • plot specification via plotly
  • translating ggplot2 plots and adding interactive elements

Plotly creates interactive plots with javascript.

plot_ly(data = penguins_std, 
        x = ~fl, 
        y = ~bl, 
        color = ~species, 
        size = 3, 
        width=420, height=300)

plotly

ggplot2 and plotly

gg <- ggplot(data=penguins_std, aes(x = fl, 
                                    y = bl, 
                                    colour = species)) +  
  geom_point(alpha=0.5) + 
  geom_smooth(method = "lm", se=F)
ggplotly(gg, width=600, height=490)

Scatterplot(ly) matrix

library(GGally)
p <- ggpairs(penguins_std, columns=2:5, 
             mapping = ggplot2::aes(color = species, 
                                    alpha = 0.8))
ggplotly(p, width=600, height=490)

Maps

data(canada.cities, package = "maps")
viz <- ggplot(canada.cities, aes(long, lat)) +
  borders(regions = "canada") +
  coord_equal() +
  geom_point(aes(text = name, size = log2(pop)), 
             colour = "red", alpha = 1/4) +
  theme_map()
ggplotly(viz)

Not all ggplot2 geoms are supported in plotly, but when they are, they just work out of the box

Modifying plotly

plotly uses elements of crosstalk to provide additional interactivity, such as linked highlighting

txh_shared <- highlight_key(txhousing, ~year)

p <- ggplot(txh_shared, aes(month, median)) +
   geom_line(aes(group = year)) + 
   geom_smooth(data = txhousing, method = "gam") + 
   scale_x_continuous("", breaks=seq(1, 12, 1),
        labels=c("J", "F", "M", "A", "M", "J", 
                 "J", "A", "S", "O", "N", "D")) +
   scale_y_continuous("Median price ('00,000)", 
                      breaks = seq(0,300000,100000),
                      labels = seq(0,3,1)) +
   facet_wrap(~ city)

gg <- ggplotly(p, height = 750, width = 900) %>%
   plotly::layout(title = "Click on a line to highlight a year")

highlight(gg)

The power of crosstalk

# install.packages("tsibble")
# install.packages("tsibbletalk")
# install.packages("feasts")
library(tsibble)
library(tsibbletalk)
library(feasts)
tourism_shared <- tourism |>
  as_shared_tsibble(spec = (State / Region) * Purpose)

tourism_feat <- tourism_shared |>
  features(Trips, feat_stl)

p1 <- tourism_shared |>
  ggplot(aes(x = Quarter, y = Trips)) +
  geom_line(aes(group = Region), alpha = 0.5) +
  facet_wrap(~ Purpose, scales = "free_y") +
  theme(axis.title = element_text(family="Helvetica"),
        axis.text = element_text(family="Helvetica"),
        legend.title = element_text(family="Helvetica"),
        legend.text = element_text(family="Helvetica"))
p2 <- tourism_feat |>
  ggplot(aes(x = trend_strength, y = seasonal_strength_year)) +
  geom_point(aes(group = Region)) +
  xlab("trend") + ylab("season") +
  theme(axis.title = element_text(family="Helvetica"),
        axis.text = element_text(family="Helvetica"),
        legend.title = element_text(family="Helvetica"),
        legend.text = element_text(family="Helvetica"),
        plot.title = element_text(family="Helvetica"))

The shared data objects from crosstalk make linking between plots easier!

Code
subplot(
    ggplotly(p1, tooltip = "Region", width = 1200, height = 600) |>
  config(displayModeBar = FALSE),
    ggplotly(p2, tooltip = "Region", width = 1000, height = 500) |>
  config(displayModeBar = FALSE),
    nrows = 1, widths=c(0.5, 0.5), heights=1) |>
  highlight(dynamic = FALSE)

Your Turn

  • get the code from the previous plot to run in your RStudio session
  • the function highlight allows to make modifications in how highlighted values are presented in plotly. Read through the parameter details in ?highlight.
  • the parameter dynamic is set to FALSE by default. Turn it to TRUE. What is the effect?

Animations

  • gganimate (Lin-Pederson) allows to make and save animations: gganimate cheat sheet
  • Animations are different from interactive graphics in that the viewer does not have any control
  • useful for different important stages of a visualization (e.g. time) and to keep track of how different visualizations are related
  • can also be used in talks

An example animation

Countries are colored manually by country_colors (hue shows continent, saturation is individual country)

gganimate

  1. Start with a ggplot2 specification
  2. Add layers with graphical primitives (geoms)
  3. Add formatting specifications
  4. Add animation specifications

A simple example

  1. Start by passing the data to ggplot
ggplot(economics) #<<

Thanks to Mitch O’Hara Wild for the example

A simple example

  1. add the mapping
ggplot(economics) +
  aes(date, unemploy) #<<

Thanks to Mitch O’Hara Wild for the example

A simple example

  1. Add a graphical primitive, let’s do a line
ggplot(economics) +
  aes(date, unemploy) +
  geom_line() #<<

Thanks to Mitch O’Hara Wild for the example

A simple example

  1. Just one extra line turns this into an animation!
ggplot(economics) +
  aes(date, unemploy) +
  geom_line() +
  transition_reveal(date) #<<

Thanks to Mitch O’Hara Wild for the example

A not-so-simple example

Using the the datasaurus dozen, again, we first pass in the dataset to ggplot

ggplot(datasaurus_dozen)#<<

A not-so-simple example

For each dataset we have x and y values, in addition we can map dataset to color

ggplot(datasaurus_dozen) +
  aes(x, y, color=dataset) #<<

A not-so-simple example

Trying a simple scatter plot first, but there is too much information

ggplot(datasaurus_dozen) +
  aes(x, y, color=dataset) +
  geom_point() + #<<
  xlim(c(0,100)) + ylim(c(0,100)) +
  coord_equal() 

A not-so-simple example

We can use facets to split up by dataset, revealing the different distributions

ggplot(datasaurus_dozen) +
  aes(x, y, color=dataset) +
  geom_point() +
  facet_wrap(~dataset) + #<<
  xlim(c(0,100)) + ylim(c(0,100)) +
  coord_equal() +
  theme(legend.position = "none")

A not-so-simple example

We can just as easily turn it into an animation, transitioning between dataset states!

ggplot(datasaurus_dozen) +
  aes(x, y) +
  geom_point() +
  xlim(c(0,100)) + ylim(c(0,100)) +
  coord_equal() +
  transition_states(dataset, 2, 3) + #<<
  labs(title = "Dataset: {closest_state}") #<<

Your Turn

The datasaurus_dozen data set is part of the R package datasauRus.

  • Load the gganimate package and get the animation from the previous page to run in your R session (might take a moment)
  • The function transition_states drives the animation. It has values 2 and 3. What do these values mean? Read up on their meaning and change them.

Controlling an animation

We control plot movement with (a grammar of animation):

  • Transitions: transition_*() define how the data should be spread out and how it relates to itself across time.
  • Views: view_*() defines how the positional scales should change along the animation.
  • Shadows: shadow_*() defines how data from other points in time should be presented in the given point in time.
  • Entrances/Exits: enter_*() and exit_*() define how new data should appear and how old data should disappear during the course of the animation.
  • Easing: ease_aes() defines how different aesthetics should be eased during transitions.

Your Turn

The gapminder example from the beginning has the static part shown below

Add animation parts! adding transition_time(year) results in the visualisation from the start.

What other animation are helpful? What views do you want to set? Maybe a shadow looks interesting?

ggplot(gapminder, aes(gdpPercap, lifeExp, size = pop, colour = country)) +
  geom_point(alpha = 0.7) +
  scale_colour_manual(values = country_colors) +
  scale_size("Population size", range = c(2, 12), breaks=c(1*10^8, 2*10^8, 5*10^8, 10^9, 2*20^9)) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme(legend.position = "bottom") +
  guides(colour = "none") 

Resources