Interactive Visualizations in R

Author

Martin Schweinberger

Published

January 1, 2026

Introduction

This tutorial introduces interactive data visualisation in R — a set of techniques that allow readers to explore data directly within a web page or HTML document, rather than passively viewing a static figure. Interactive visualisations let users zoom, pan, filter, hover for details, and animate changes over time, making them especially well-suited to publishing research results online, building data dashboards, and communicating findings to non-specialist audiences.

Interactive graphics cannot be embedded in printed documents, but they are ideally suited to modern research outputs: websites, HTML reports, Quarto documents, R Markdown files, and Shiny apps. In applied linguistics and corpus linguistics, interactive visualisations are particularly useful for exploring large datasets — such as the diachronic frequencies of hundreds of lexical items — where static plots would be too crowded to read.

This tutorial covers four main tools, all of which produce self-contained HTML widgets that require no external server:

  • plotly (Sievert 2020) for interactive charts (scatter plots, line graphs, bar charts, bubble charts, histograms)
  • gganimate (Pedersen and Robinson 2022) for animated ggplot2 graphics that transition through time
  • leaflet (Cheng, Karambelkar, and Xie 2023) for interactive maps with zoom, pan, markers, and popups
  • DT for interactive, searchable, sortable data tables
Learning Objectives

By the end of this tutorial you will be able to:

  1. Explain what interactive visualisation is and when it is appropriate to use it
  2. Create interactive scatter plots, line charts, bar charts, bubble charts, and histograms using plotly
  3. Convert a static ggplot2 graphic into an animated GIF using gganimate
  4. Create animated bubble plots that transition through time using plotly
  5. Build interactive maps with tile layers, custom markers, popups, and colour-coded polygon layers using leaflet
  6. Display world map data with choropleth colouring using sf and leaflet
  7. Create interactive, searchable, sortable data tables using DT
  8. Embed interactive visualisations in Quarto and R Markdown documents
Prerequisite Tutorials
Citation

Martin Schweinberger. 2026. Interactive Visualizations in R. The Language Technology and Data Analysis Laboratory (LADAL), The University of Queensland, Australia. url: https://ladal.edu.au/tutorials/motion/motion.html (Version 2026.03.30).

Preparation and Session Set-up

Install required packages once:

Code
install.packages("plotly")
install.packages("gganimate")
install.packages("gapminder")
install.packages("leaflet")
install.packages("DT")
install.packages("sf")
install.packages("rnaturalearth")       # world country polygons
install.packages("rnaturalearthdata")   # data dependency for rnaturalearth
install.packages("dplyr")
install.packages("tidyr")
install.packages("ggplot2")
install.packages("flextable")
install.packages("gifski")    # required for gganimate GIF rendering
install.packages("checkdown")

Load packages for this session:

Code
library(checkdown)   # interactive exercises
library(dplyr)       # data manipulation
library(tidyr)       # data reshaping
library(ggplot2)     # static graphics (base for gganimate)
library(gganimate)   # animated ggplot2 graphics
library(gapminder)   # gapminder dataset
library(plotly)      # interactive charts
library(leaflet)     # interactive maps
library(DT)          # interactive tables
library(sf)              # spatial data (replaces deprecated maptools)
library(rnaturalearth)     # world country polygons
library(rnaturalearthdata) # data dependency for rnaturalearth
library(flextable)   # formatted static tables

The Linguistic Dataset

Throughout the charts and animations sections of this tutorial we use a dataset called coocdata, which records how often adjectives in a large historical corpus of English were amplified by various degree adverbs (such as very, really, totally) across different decades. This type of data arises in corpus-linguistic research on amplifier variation — the study of how intensifying degree adverbs compete with one another and how their frequencies change over time (Lorenz 2002).

The dataset allows us to track, for example, whether the share of very as an amplifier of a given adjective has increased or decreased over the decades — a classical question in diachronic corpus linguistics concerning the grammaticalisation and renewal of intensifiers. Visualising such diachronic change interactively is particularly useful because the corpus contains many adjectives, and a static plot would be too cluttered to interpret.

Code
# load data
coocdata <- base::readRDS("tutorials/motion/data/coo.rda", "rb")

Decade

Amp

Adjective

N.Amp.

N.Adj.

OBS

EXP

Probability

CollStrength

OddsRatio

p

attr

sig

1,870

pretty

amiable

7

4

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

amusing

7

4

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

angry

7

8

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

annoyed

7

1

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

bad

7

7

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

beautiful

7

16

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

busy

7

108

0

0.1

0.02

0

0

1

repel

n.s.

1,870

pretty

charming

7

7

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

clever

7

4

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

comfortable

7

7

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

contemptible

7

2

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

convenient

7

13

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

dangerous

7

44

0

0.1

0.01

0

0

1

repel

n.s.

1,870

pretty

dark

7

11

0

0.0

0.00

0

0

1

repel

n.s.

1,870

pretty

different

7

202

0

0.2

0.03

0

0

1

repel

n.s.

We process the data to calculate, for each adjective and decade, the percentage of occurrences amplified by very versus all other amplifiers combined. Adjectives amplified by neither are excluded.

Code
coocs <- coocdata |>
  dplyr::select(Decade, Amp, Adjective, OBS) |>
  dplyr::rename(Frequency = OBS, Amplifier = Amp) |>
  dplyr::mutate(Amplifier = ifelse(Amplifier == "very", "very", "other")) |>
  dplyr::group_by(Decade, Adjective, Amplifier) |>
  dplyr::summarise(Frequency = sum(Frequency), .groups = "drop") |>
  tidyr::spread(Amplifier, Frequency) |>
  dplyr::group_by(Decade, Adjective) |>
  dplyr::mutate(
    Frequency_Adjective = sum(other + very, na.rm = TRUE),
    Percent_very = round(very / (other + very) * 100, 2)
  ) |>
  dplyr::mutate(
    Percent_very = ifelse(is.na(Percent_very), 0, Percent_very),
    Adjective    = factor(Adjective)
  )

Decade

Adjective

other

very

Frequency_Adjective

Percent_very

1,870

amiable

0

0

0

0

1,870

amusing

0

0

0

0

1,870

angry

0

0

0

0

1,870

annoyed

0

0

0

0

1,870

bad

0

0

0

0

1,870

beautiful

0

0

0

0

1,870

busy

1

0

1

0

1,870

charming

0

0

0

0

1,870

clever

0

0

0

0

1,870

comfortable

0

0

0

0

We also create a decade-level summary for use in the simpler chart examples:

Code
scdat <- coocs |>
  dplyr::group_by(Decade) |>
  dplyr::summarise(Percent_very = mean(Percent_very, na.rm = TRUE), .groups = "drop")

Interactive Charts with plotly

Section Overview

What you will learn: How to create interactive scatter plots, line charts, bar charts, bubble charts, and histograms using plotly.

Key advantage: plotly charts are fully self-contained HTML widgets — hover effects, zoom, pan, and download buttons are included automatically with no extra code. They can be embedded directly in any Quarto or R Markdown document.

The plotly package (Sievert 2020) is the most widely used interactive graphics library for R. It works in two ways: you can either use plot_ly() to build charts directly with plotly syntax, or you can build a ggplot2 chart and convert it to an interactive version with a single call to ggplotly(). The latter approach is particularly useful because it means you can use the full power of ggplot2 (Wickham 2016) for chart design and then add interactivity with minimal extra effort.

Scatter plots

A scatter plot is appropriate when you want to show the relationship between two continuous variables. Here we plot the mean percentage of very-amplification by decade.

Code
plot_ly(
  scdat,
  x = ~Decade,
  y = ~Percent_very,
  type   = "scatter",
  mode   = "markers",
  marker = list(size = 10, color = "steelblue"),
  text   = ~paste0("Decade: ", Decade, "<br>% very: ", round(Percent_very, 1)),
  hoverinfo = "text"
) |>
  layout(
    title  = "Mean percentage of 'very'-amplification by decade",
    xaxis  = list(title = "Decade"),
    yaxis  = list(title = "Mean % amplified by 'very'")
  )

Hover over any point to see the decade and percentage value. Use the toolbar in the top-right corner to zoom, pan, or download the figure.

Line charts

A line chart is appropriate for the same data when you want to emphasise continuity and trend over time. We add mode = "lines+markers" to show both the connecting line and the data points.

Code
plot_ly(
  scdat,
  x = ~Decade,
  y = ~Percent_very,
  type   = "scatter",
  mode   = "lines+markers",
  line   = list(color = "steelblue", width = 2),
  marker = list(size = 8, color = "steelblue"),
  text   = ~paste0("Decade: ", Decade, "<br>% very: ", round(Percent_very, 1)),
  hoverinfo = "text"
) |>
  layout(
    title = "Diachronic trend: 'very'-amplification across decades",
    xaxis = list(title = "Decade"),
    yaxis = list(title = "Mean % amplified by 'very'")
  )

Bar charts

Bar charts are effective for comparing values across discrete categories. Note the interactive legend: click on categories to toggle their visibility.

Code
plot_ly(
  scdat,
  x    = ~Decade,
  y    = ~Percent_very,
  type = "bar",
  marker = list(
    color = ~Percent_very,
    colorscale = "Blues",
    showscale  = TRUE
  ),
  text      = ~paste0(round(Percent_very, 1), "%"),
  hoverinfo = "text+x"
) |>
  layout(
    title = "Percentage of 'very'-amplification by decade",
    xaxis = list(title = "Decade"),
    yaxis = list(title = "Mean % amplified by 'very'")
  )

Bubble charts

Bubble charts add a third dimension to a scatter plot by encoding a third variable as the size of each point. Here we use the adjective-level data: each bubble represents one adjective in one decade. The x-axis shows the decade, the y-axis the percentage amplified by very, and the bubble size the total frequency of that adjective in the corpus.

Code
# use a sample of adjectives to avoid overplotting
bubble_data <- coocs |>
  dplyr::filter(Adjective %in% c("good", "great", "nice", "bad",
                                  "big", "old", "new", "long")) |>
  dplyr::filter(!is.na(Frequency_Adjective))

plot_ly(
  bubble_data,
  x    = ~Decade,
  y    = ~Percent_very,
  size = ~Frequency_Adjective,
  color = ~Adjective,
  type  = "scatter",
  mode  = "markers",
  sizes = c(5, 50),
  text  = ~paste0(Adjective, "<br>Decade: ", Decade,
                  "<br>% very: ", Percent_very,
                  "<br>Freq: ",   Frequency_Adjective),
  hoverinfo = "text"
) |>
  layout(
    title  = "Amplification by 'very' across adjectives and decades",
    xaxis  = list(title = "Decade"),
    yaxis  = list(title = "% amplified by 'very'"),
    legend = list(title = list(text = "Adjective"))
  )

Click on adjective names in the legend to show or hide individual items. The bubble size reflects how frequently the adjective appeared in the corpus — larger bubbles indicate more data.

Histograms

Histograms are appropriate for visualising the distribution of a single continuous variable. Here we show the distribution of very-amplification percentages across all adjectives and decades.

Code
plot_ly(
  coocs |> dplyr::filter(!is.na(Percent_very)),
  x    = ~Percent_very,
  type = "histogram",
  nbinsx = 30,
  marker = list(color = "steelblue", line = list(color = "white", width = 0.5))
) |>
  layout(
    title = "Distribution of 'very'-amplification percentages",
    xaxis = list(title = "% amplified by 'very'"),
    yaxis = list(title = "Count")
  )
ggplot2 to plotly with ggplotly()

Any ggplot2 plot can be converted to an interactive plotly chart with a single call to ggplotly(). This is the easiest way to add interactivity to an existing static plot:

p <- ggplot(scdat, aes(x = Decade, y = Percent_very)) +
  geom_line(colour = "steelblue") +
  geom_point(size = 3) +
  labs(title = "Diachronic trend: 'very'",
       x = "Decade", y = "% amplified by 'very'") +
  theme_bw()

ggplotly(p)

Exercises: Interactive Charts

Q1. What is the key advantage of ggplotly() over building a chart directly with plot_ly()?





Q2. When is a bubble chart more informative than a standard scatter plot?






Animations

Section Overview

What you will learn: How to create animated graphics that transition through time using gganimate, and how to build an animated bubble plot using plotly.

Key concept: Animation is most appropriate when you have a temporal variable and want to show how a distribution or relationship changes over time — as opposed to a static faceted plot where all time points are shown simultaneously.

Animations are particularly powerful for communicating diachronic change. Rather than presenting all time points at once in a cluttered facet grid, an animation guides the viewer’s attention sequentially through the data.

Animations with gganimate

The gganimate package (Pedersen and Robinson 2022) extends ggplot2 (Wickham 2016) with a grammar of animation. You build a standard ggplot2 plot and then add one of several transition_*() functions to specify how the data should change across frames. The most common is transition_time(), which transitions between observations at different values of a time variable.

We use the gapminder dataset, which contains data on life expectancy, GDP per capita, and population for countries around the world from 1952 to 2007. This is a classic dataset for animated visualisation because the trends over time are striking and interpretable.

country

continent

year

lifeExp

pop

gdpPercap

Afghanistan

Asia

1,952

28.801

8,425,333

779.4453145

Afghanistan

Asia

1,957

30.332

9,240,934

820.8530296

Afghanistan

Asia

1,962

31.997

10,267,083

853.1007100

Afghanistan

Asia

1,967

34.020

11,537,966

836.1971382

Afghanistan

Asia

1,972

36.088

13,079,460

739.9811058

Afghanistan

Asia

1,977

38.438

14,880,372

786.1133600

Afghanistan

Asia

1,982

39.854

12,881,816

978.0114388

Afghanistan

Asia

1,987

40.822

13,867,957

852.3959448

Afghanistan

Asia

1,992

41.674

16,317,921

649.3413952

Afghanistan

Asia

1,997

41.763

22,227,415

635.3413510

We begin with a static snapshot to verify the plot looks correct before animating it:

Code
p <- ggplot(
  gapminder,
  aes(x = gdpPercap, y = lifeExp, size = pop, colour = continent)
) +
  geom_point(alpha = 0.7) +
  scale_colour_viridis_d() +
  scale_size(range = c(2, 12)) +
  scale_x_log10(labels = scales::comma) +
  labs(
    x      = "GDP per capita (log scale)",
    y      = "Life expectancy (years)",
    colour = "Continent",
    size   = "Population"
  ) +
  theme_bw()
p

Now we add the animation. transition_time(year) tells gganimate to step through the data one year at a time. The {frame_time} label in the title updates automatically with the current year displayed.

Code
anim <- p +
  transition_time(year) +
  labs(title = "Year: {frame_time}") +
  ease_aes("linear")

animate(anim, nframes = 100, fps = 10, width = 700, height = 500)

Rendering gganimate Animations

gganimate produces animations by rendering many frames of a ggplot2 plot and combining them. The default output is a GIF via the gifski package (install with install.packages("gifski")). You can save the animation with anim_save("my_animation.gif"). Inside a Quarto document, setting eval=FALSE and displaying a pre-rendered GIF (as above) is the most reliable approach, since rendering many frames in a knit session can be slow.

Additional gganimate example: diachronic bar chart

Here we apply animation to the linguistic data. We animate a bar chart showing the mean very-amplification percentage across decades, so the viewer watches the bar rise and fall over time.

Code
bar_anim <- ggplot(scdat, aes(x = "very", y = Percent_very, fill = Percent_very)) +
  geom_col(width = 0.5) +
  scale_fill_gradient(low = "lightblue", high = "steelblue") +
  labs(
    title   = "Decade: {closest_state}",
    x       = NULL,
    y       = "Mean % amplified by 'very'",
    fill    = "% very"
  ) +
  ylim(0, 100) +
  theme_bw() +
  theme(legend.position = "none") +
  transition_states(Decade, transition_length = 2, state_length = 1) +
  enter_fade() +
  exit_fade()

animate(bar_anim, nframes = 80, fps = 8, width = 400, height = 400)

Animated bubble plot with plotly

plotly also supports animation via the frame argument. Setting frame = ~year creates an animated plot where pressing the play button steps through one frame per year. Unlike GIF-based animations, plotly animations are fully interactive: you can pause, scrub to any frame, and hover over individual points.

Code
gapminder |>
  plot_ly(
    x         = ~gdpPercap,
    y         = ~lifeExp,
    size      = ~pop,
    color     = ~continent,
    frame     = ~year,
    text      = ~country,
    hoverinfo = "text",
    type      = "scatter",
    mode      = "markers",
    sizes     = c(5, 60)
  ) |>
  layout(
    xaxis = list(type = "log", title = "GDP per capita (log scale)"),
    yaxis = list(title = "Life expectancy (years)"),
    title = "Global development: GDP vs life expectancy over time"
  ) |>
  animation_opts(frame = 100, easing = "linear", redraw = FALSE) |>
  animation_button(x = 1, xanchor = "right", y = 0, yanchor = "bottom")

Press the Play button to start the animation. Hover over any bubble to see the country name.


Exercises: Animations

Q1. What does the transition_time() function in gganimate do?





Q2. What is the key difference between a gganimate GIF animation and a plotly animated chart?






Interactive Maps

Section Overview

What you will learn: How to build interactive maps using leaflet: a simple tile-based map, a map with custom markers and popups, and a choropleth world map with colour-coded country polygons using sf data.

Key tools: leaflet (Cheng, Karambelkar, and Xie 2023) for interactive web maps; sf (Pebesma 2018) for handling spatial data.

The leaflet package wraps the popular Leaflet.js JavaScript library for R. It produces interactive web maps that support zoom, pan, markers, popups, polygons, and legends — all without any JavaScript knowledge required. The sf package (Pebesma 2018) provides the modern R framework for handling spatial vector data (points, lines, polygons), replacing older packages such as sp and maptools.

Basic tile map

The simplest leaflet map loads a tile layer (a background map from a provider such as OpenStreetMap) and centres the view on a location. Here we centre on Brisbane.

Code
leaflet() |>
  setView(lng = 153.05, lat = -27.45, zoom = 12) |>
  addTiles()

Use the + / - buttons or scroll wheel to zoom in and out. Click and drag to pan.

Map with markers and popups

Markers identify specific locations. Popups display information when a marker is clicked. Here we add markers for the five main campuses of The University of Queensland.

Code
uq_campuses <- data.frame(
  name = c("St Lucia (main campus)", "Gatton", "Herston",
           "Dutton Park", "Stradbroke Island"),
  lat  = c(-27.4975, -27.5441, -27.4530, -27.4993, -27.4944),
  lng  = c(153.0137, 152.3375, 153.0280, 153.0270, 153.4282),
  info = c(
    "Main campus — home of LADAL",
    "Agricultural and veterinary sciences",
    "Medical and health sciences",
    "Oral health centre",
    "Research station on Minjerribah"
  )
)

leaflet(uq_campuses) |>
  setView(lng = 152.7, lat = -27.5, zoom = 8) |>
  addTiles() |>
  addMarkers(
    lng   = ~lng,
    lat   = ~lat,
    popup = ~paste0("<b>", name, "</b><br>", info),
    label = ~name
  )

Click any marker to open its popup. Hover over a marker to see its label.

Custom marker colours

Different marker colours help distinguish categories. Here we use coloured circle markers to distinguish LADAL tutorial topics across three UQ locations.

Code
locations <- data.frame(
  place   = c("LADAL Office",    "UQ Library",      "Social Sciences Building"),
  lat     = c(-27.4975,          -27.4972,           -27.4965),
  lng     = c(153.0137,          153.0125,            153.0140),
  type    = c("office",          "library",          "teaching"),
  colour  = c("blue",            "red",               "green")
)

leaflet(locations) |>
  setView(lng = 153.014, lat = -27.497, zoom = 16) |>
  addTiles() |>
  addCircleMarkers(
    lng    = ~lng,
    lat    = ~lat,
    color  = ~colour,
    radius = 10,
    popup  = ~paste0("<b>", place, "</b><br>Type: ", type),
    label  = ~place
  )

Choropleth world map

A choropleth map colours geographic regions by the value of a variable. We use rnaturalearth::ne_countries() to load world country polygons as an sf object, calculate population density from the built-in population estimate column, and display it as an interactive choropleth with leaflet.

Code
# Load world country polygons from rnaturalearth
# Requires: install.packages(c("rnaturalearth", "rnaturalearthdata"))
world <- rnaturalearth::ne_countries(scale = "medium", returnclass = "sf")

# Calculate population density (people per km²) using sf area calculation
world <- world |>
  dplyr::mutate(
    area_km2    = as.numeric(sf::st_area(geometry)) / 1e6,
    pop_density = round(pop_est / area_km2, 2),
    pop_density = dplyr::if_else(is.infinite(pop_density) | is.nan(pop_density),
                                 NA_real_, pop_density)
  )

# Colour palette: quantile-based so variation is visible across the full range
pal <- colorQuantile(
  palette  = rev(viridis::viridis(10)),
  domain   = world$pop_density,
  n        = 10,
  na.color = "#cccccc"
)

# Build the map
leaflet(world) |>
  setView(lng = 0, lat = 20, zoom = 2) |>
  addTiles() |>
  addPolygons(
    fillColor   = ~pal(pop_density),
    fillOpacity = 0.8,
    color       = "#333333",
    weight      = 0.5,
    opacity     = 1,
    label       = ~paste0(name_long, ": ",
                          ifelse(is.na(pop_density), "no data",
                                 paste0(formatC(pop_density, format = "f",
                                                digits = 1), " per km\u00b2"))),
    labelOptions     = labelOptions(direction = "auto"),
    highlightOptions = highlightOptions(
      color        = "#000000",
      weight       = 2,
      bringToFront = TRUE
    )
  ) |>
  addLegend(
    position = "bottomright",
    pal      = pal,
    values   = ~pop_density,
    title    = "Population density<br>(decile)",
    opacity  = 0.8
  )

Hover over any country to see its name and population density. The colour scale uses quantiles so that variation is visible even where most countries have low densities.


Exercises: Interactive Maps

Q1. What is the role of addTiles() in a leaflet map?





Q2. Why is a quantile-based colour scale (colorQuantile()) often preferred over a linear colour scale for choropleth maps?






Interactive Tables with DT

Section Overview

What you will learn: How to create interactive, searchable, sortable data tables using the DT package — a particularly useful tool for sharing corpus linguistic results where readers may want to filter and explore a large output table.

Key advantage: A DT table requires no additional coding beyond a single function call and works in any HTML output format. It is ideal for supplementary data tables in online publications.

The DT package provides an R interface to the DataTables JavaScript library. A datatable() call wraps any R data frame in a fully interactive HTML table with built-in search, column sorting, pagination, and optional download buttons. This is particularly valuable in corpus linguistics and lexicography, where results tables — concordance lines, frequency lists, collocations, dictionary entries — may contain hundreds or thousands of rows.

Basic interactive table

Here we display the processed coocs amplifier dataset as an interactive table. Every column is immediately sortable by clicking the column header; the search box filters across all columns simultaneously.

Code
coocs |>
  dplyr::select(Decade, Adjective, very, other, Frequency_Adjective, Percent_very) |>
  dplyr::rename(
    `Freq (very)`  = very,
    `Freq (other)` = other,
    `Total freq`   = Frequency_Adjective,
    `% very`       = Percent_very
  ) |>
  as.data.frame() |>
  datatable(
    caption  = "Interactive table: degree adverb amplification data by decade and adjective.",
    filter   = "top",
    rownames = FALSE,
    options  = list(
      pageLength = 15,
      scrollX    = TRUE,
      dom        = "lfrtip"
    )
  )

Use the search boxes at the top of each column to filter by decade, adjective, or frequency range. Click any column header to sort ascending or descending.

Table with conditional formatting

We can add visual formatting — such as colour bars or highlighting — to draw attention to notable values. Here we colour the % very column with a gradient from white to steelblue.

Code
coocs |>
  dplyr::select(Decade, Adjective, Percent_very) |>
  dplyr::arrange(-Percent_very) |>
  as.data.frame() |>
  datatable(
    caption  = "Adjectives ranked by percentage amplified by 'very'.",
    rownames = FALSE,
    options  = list(pageLength = 15, scrollX = TRUE)
  ) |>
  formatStyle(
    "Percent_very",
    background = styleColorBar(range(coocs$Percent_very, na.rm = TRUE), "steelblue"),
    backgroundSize   = "100% 80%",
    backgroundRepeat = "no-repeat",
    backgroundPosition = "center"
  )

Table with download buttons

For sharing results with collaborators, you can add buttons that allow users to download the table as a CSV, Excel, or PDF file directly from the web page:

Code
coocs |>
  dplyr::group_by(Adjective) |>
  dplyr::summarise(
    Mean_pct_very = round(mean(Percent_very, na.rm = TRUE), 2),
    Total_freq    = sum(Frequency_Adjective, na.rm = TRUE),
    .groups = "drop"
  ) |>
  dplyr::arrange(-Mean_pct_very) |>
  as.data.frame() |>
  datatable(
    caption  = "Summary table: mean 'very'-amplification rate per adjective (all decades combined).",
    rownames = FALSE,
    extensions = "Buttons",
    options  = list(
      pageLength = 15,
      dom        = "Blfrtip",
      buttons    = c("copy", "csv", "excel")
    )
  )

Exercises: Interactive Tables

Q1. What is the main advantage of a DT::datatable() table over a standard flextable() or knitr::kable() table in an HTML document?





Citation & Session Info

Citation

Martin Schweinberger. 2026. Interactive Visualizations in R. The Language Technology and Data Analysis Laboratory (LADAL), The University of Queensland, Australia. url: https://ladal.edu.au/tutorials/motion/motion.html (Version 2026.03.29).

@manual{martinschweinberger2026interactive,
  author       = {Martin Schweinberger},
  title        = {Interactive Visualizations in R},
  year         = {2026},
  note         = {https://ladal.edu.au/tutorials/motion/motion.html},
  organization = {The Language Technology and Data Analysis Laboratory (LADAL), The University of Queensland, Australia},
  edition      = {2026.03.29},
  doi          = {}
}
Code
sessionInfo()
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: Australia/Brisbane
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] rnaturalearthdata_1.0.0 rnaturalearth_1.2.0     sf_1.0-19              
 [4] flextable_0.9.11        DT_0.33                 leaflet_2.2.2          
 [7] plotly_4.12.0           gapminder_1.0.0         gganimate_1.0.9        
[10] ggplot2_4.0.2           tidyr_1.3.2             dplyr_1.2.0            
[13] checkdown_0.0.13       

loaded via a namespace (and not attached):
 [1] tidyselect_1.2.1        viridisLite_0.4.2       farver_2.1.2           
 [4] viridis_0.6.5           S7_0.2.1                fastmap_1.2.0          
 [7] lazyeval_0.2.2          tweenr_2.0.3            fontquiver_0.2.1       
[10] digest_0.6.39           lifecycle_1.0.5         magrittr_2.0.4         
[13] compiler_4.4.2          rlang_1.1.7             sass_0.4.9             
[16] progress_1.2.3          tools_4.4.2             yaml_2.3.10            
[19] data.table_1.17.0       knitr_1.51              askpass_1.2.1          
[22] prettyunits_1.2.0       htmlwidgets_1.6.4       classInt_0.4-11        
[25] xml2_1.3.6              RColorBrewer_1.1-3      KernSmooth_2.23-24     
[28] withr_3.0.2             purrr_1.2.1             grid_4.4.2             
[31] gdtools_0.5.0           e1071_1.7-16            scales_1.4.0           
[34] cli_3.6.5               rmarkdown_2.30          crayon_1.5.3           
[37] ragg_1.5.1              generics_0.1.4          rstudioapi_0.17.1      
[40] httr_1.4.7              commonmark_2.0.0        DBI_1.2.3              
[43] cachem_1.1.0            proxy_0.4-27            BiocManager_1.30.27    
[46] s2_1.1.7                vctrs_0.7.2             jsonlite_2.0.0         
[49] fontBitstreamVera_0.1.1 litedown_0.9            hms_1.1.4              
[52] patchwork_1.3.0         systemfonts_1.3.1       magick_2.8.5           
[55] crosstalk_1.2.1         jquerylib_0.1.4         units_0.8-5            
[58] glue_1.8.0              codetools_0.2-20        stringi_1.8.7          
[61] gtable_0.3.6            tibble_3.3.1            pillar_1.11.1          
[64] htmltools_0.5.9         openssl_2.3.2           R6_2.6.1               
[67] wk_0.9.4                textshaping_1.0.0       evaluate_1.0.5         
[70] markdown_2.0            renv_1.1.7              fontLiberation_0.1.0   
[73] bslib_0.9.0             class_7.3-22            Rcpp_1.1.1             
[76] zip_2.3.2               uuid_1.2-1              gridExtra_2.3          
[79] officer_0.7.3           xfun_0.56               pkgconfig_2.0.3        
AI Transparency Statement

This tutorial was re-developed with the assistance of Claude (claude.ai), a large language model created by Anthropic. Claude was used to help revise the tutorial text, structure the instructional content, generate the R code examples, and write the checkdown quiz questions and feedback strings. All content was reviewed, edited, and approved by the author (Martin Schweinberger), who takes full responsibility for the accuracy and pedagogical appropriateness of the material. The use of AI assistance is disclosed here in the interest of transparency and in accordance with emerging best practices for AI-assisted academic content creation.

Back to top

Back to HOME

References

Cheng, Joe, Bhaskar Karambelkar, and Yihui Xie. 2023. Leaflet: Create Interactive Web Maps with the JavaScript ’Leaflet’ Library. https://CRAN.R-project.org/package=leaflet.
Lorenz, Gunter. 2002. “Really Worthwhile or Not Really Significant? A Corpus-Based Approach to the Delexicalization and Grammaticalization of Intensifiers in Modern English.” In New Reflections on Grammaticalization, edited by Ilse Wischer and Gabriele Diewald, 49:143–61. Typological Studies in Language. Amsterdam; Philadelphia: John Benjamins. https://doi.org/10.1075/tsl.49.11lor.
Pebesma, Edzer. 2018. “Simple Features for R: Standardized Support for Spatial Vector Data.” The R Journal 10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009.
Pedersen, Thomas Lin, and David Robinson. 2022. Gganimate: A Grammar of Animated Graphics. https://CRAN.R-project.org/package=gganimate.
Sievert, Carson. 2020. Interactive Web-Based Data Visualization with R, Plotly, and Shiny. Boca Raton, FL: Chapman; Hall/CRC. https://doi.org/10.1201/9780429447273.
Wickham, Hadley. 2016. Ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. https://ggplot2.tidyverse.org.