Rows: 20,778
Columns: 13
$ name <chr> "Amy", "Amy", "Amy", "Amy", "Amy", "Amy",…
$ year <dbl> 1975, 1975, 1975, 1975, 1975, 1975, 1975,…
$ month <dbl> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,…
$ day <int> 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 2…
$ hour <dbl> 0, 6, 12, 18, 0, 6, 12, 18, 0, 6, 12, 18,…
$ lat <dbl> 27.5, 28.5, 29.5, 30.5, 31.5, 32.4, 33.3,…
$ long <dbl> -79.0, -79.0, -79.0, -79.0, -78.8, -78.7,…
$ status <fct> tropical depression, tropical depression,…
$ category <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ wind <int> 25, 25, 25, 25, 25, 25, 25, 30, 35, 40, 4…
$ pressure <int> 1013, 1013, 1013, 1013, 1012, 1012, 1011,…
$ tropicalstorm_force_diameter <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ hurricane_force_diameter <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
This template follows the interactive data visualization slides. Please be sure to cross-reference the slides, which contain important conceptual context and comparisons!
Setup
We use the storms dataset from {dplyr} throughout this lab — no external file needed. It contains Atlantic storm tracks with wind speed, pressure, storm status, and geographic position from 1975 to 2022.
Key variables in storms:
| Variable | Type | Description |
|---|---|---|
name |
character | Storm name |
year, month, day, hour |
numeric | Date & time of observation |
lat, long |
numeric | Storm center position |
status |
factor | "tropical depression", "tropical storm", "hurricane", etc. |
category |
numeric | Saffir-Simpson category (1–5; NA for non-hurricanes) |
wind |
numeric | Max sustained wind speed (knots) |
pressure |
numeric | Min central pressure (millibars) |
tropicalstorm_force_diameter |
numeric | Diameter (nautical miles) of TS-force winds |
Wrangle data
Part 1: ggplotly() — converting a static ggplot
See slides: Two approaches and ggplotly()
Build a static ggplot
We start with a jitter plot of wind speed vs. pressure.
ggplot_static <- storms_clean %>%
ggplot(aes(
x = pressure,
y = wind,
color = status,
text = paste0(
"<b>", name, " (", year, ")</b><br>",
"Status: ", status, "<br>",
"Wind: ", round(wind,1), " kt<br>",
"Pressure: ", round(pressure,1), " mb"
)
)) +
geom_jitter(alpha = 0.35, size = 0.9, width = 1.5, height = 1.5) +
scale_color_brewer(palette = "Paired") +
labs(
title = "Atlantic Storm Intensity by Status",
x = "Pressure (mb)",
y = "Wind Speed (knots)",
color = "Status"
) +
theme_minimal()
ggplot_static
Convert to interactive with ggplotly()
Try it:
- Hover over any point to see the formatted label.
- Click a status category in the legend to hide it.
- Double click a status category to view only that category.
- Drag to zoom; double-click to reset.
The toolbar in the top-right gives additional controls!
Customising ggplotly() output
A few small tweaks make ggplotly() output cleaner:
Part 2: plot_ly() — native plotly for more control
See slides: plot_ly() grammar and animation
Bar chart: storm counts by status and decade
storms_clean |>
mutate(decade = paste0(floor(year / 10) * 10, "s")) |>
count(decade, status) |>
plot_ly(
x = ~decade,
y = ~n,
color = ~status,
colors = RColorBrewer::brewer.pal(8, "Paired"),
type = "bar",
text = ~paste0("<b>", status, "</b><br>n = ", n),
textposition = "none",
hoverinfo = "text" ) |>
layout(
barmode = "stack",
title = "Atlantic Storm Counts by Decade and Status",
xaxis = list(title = "Decade"),
yaxis = list(title = ""),
legend = list(title = list(text = "<b>Status</b>"))
)land <- "#2d3436"
water <- "#1a1a2e"
plot_ly(storms,
lat = ~lat,
lon = ~long,
frame = ~year,
type = "scattergeo",
mode = "markers",
color = ~as.factor(category),
colors = RColorBrewer::brewer.pal(5, "BuPu"),
marker = list(size = 6, opacity = 0.7),
text = ~paste0("<b>", name, "</b><br>Category: ", category),
hoverinfo = "text") |>
layout(
title = "Atlantic Storm Positions by Year",
geo = list(
scope = "north america",
showland = TRUE, landcolor = land,
showocean = TRUE, oceancolor = water,
showcoastlines = TRUE, coastlinecolor = "#636e72",
showlakes = TRUE, lakecolor = water,
bgcolor = water,
projection = list(type = "mercator"),
lonaxis = list(range = c(-110, -10)),
lataxis = list(range = c(5, 60))
),
paper_bgcolor = water,
font = list(color = "white")
) |>
animation_opts(frame = 1000)Part 3: Saving and embedding widgets
See slides: saveWidget() and embedding in Quarto
Save a widget to a standalone HTML file
#...............assign the animated chart to an object...........
animated_storms <- plot_ly(storms, lat = ~lat, lon = ~long,
frame = ~year, type = "scattergeo", mode = "markers",
color = ~as.factor(category), colors = RColorBrewer::brewer.pal(5, "BuPu"),
marker = list(size = 6, opacity = 0.7),
text = ~paste0("<b>", name, "</b><br>Category: ", category),
hoverinfo = "text") |>
layout(
title = "Atlantic Storm Positions by Year",
geo = list(
scope = "north america",
showland = TRUE, landcolor = "#2d3436",
showocean = TRUE, oceancolor = "#1a1a2e",
showcoastlines = TRUE, coastlinecolor = "#636e72",
showlakes = TRUE, lakecolor = "#1a1a2e",
bgcolor = "#1a1a2e",
projection = list(type = "mercator"),
lonaxis = list(range = c(-110, -10)),
lataxis = list(range = c(5, 60))
),
paper_bgcolor = "#1a1a2e",
font = list(color = "white")
) |>
animation_opts(frame = 1000, transition = 0)
#.....save as a self-contained HTML — anyone can open this file.....
saveWidget(animated_storms,
file = here::here("week9", "outputs", "storms_animated.html"),
selfcontained = TRUE)selfcontained = TRUE bundles all JavaScript and CSS into a single ~3 MB file. Set it to FALSE if you are hosting the file on a web server — this produces a smaller HTML plus a _files/ folder.
Embedding works automatically in Quarto
In a Quarto HTML document, you do not need saveWidget(). Just print the widget object inside a code chunk — Quarto handles embedding automatically when you click Render.
# Assign and print — Quarto embeds this in the rendered HTML
trend_widget <- storms_clean |>
mutate(decade = paste0(floor(year / 10) * 10, "s")) |>
count(decade, status) |>
plot_ly(
x = ~decade,
y = ~n,
color = ~status,
colors = RColorBrewer::brewer.pal(8, "Paired"),
type = "bar",
text = ~paste0("<b>", status, "</b><br>n = ", n),
textposition = "none",
hoverinfo = "text",
hovertemplate = "%{text}<extra></extra>"
) |>
layout(
barmode = "stack",
title = "Atlantic Storm Counts by Decade and Status",
xaxis = list(title = "Decade"),
yaxis = list(title = "", showticklabels = FALSE),
legend = list(title = list(text = "<b>Status</b>"))
)
trend_widget # printing here embeds it in the rendered output