Interactive Charts & Maps
Maya's one neighbourhood bakery, the shop whose Q1 numbers you turned into a report table earlier in this track, did so well that she opened five more. She now runs six coffee-and-pastry shops across the city, and a printed bar chart no longer keeps up with her questions. Which shop is that tall bar again? What exactly did the quiet one take last month? Where are they on the map?
A static chart is a photograph: it shows the shape and then stops answering. An interactive chart is a conversation. You hover a point and it tells you which shop and the exact dollars; you drag a box around a crowded corner and it zooms in; you pan a map and the streets move under your shops. Move the Area filter on the dashboard below and watch every number and chart answer at once. That responsiveness is what this lesson builds, one chart and one map at a time.
By the end of this lesson you will be able to:
- Say what interactivity actually adds to a chart, and when it helps versus when it hurts
- Turn an ordinary ggplot into an interactive plotly chart with one line,
ggplotly() - Put your data on a leaflet map: a street basemap with a marker and a popup for every row
- Choose between an interactive and a static chart by where it has to live
Prerequisites: you can run R and load a package with library(), and you have built a ggplot (data, then aes(), then a geom) in the ggplot2 course. Every new term is defined as it appears.
What interactivity actually buys you
A good static chart answers the big-picture question well: do busier shops make more money? Maya can read that off a scatter in a second. But the moment she asks a pointed question, a static picture goes quiet. Three things interactivity adds, each fixing a real limit of a printed chart:
- Hover reveals the exact value and the identity behind a mark. Instead of "that dot is around forty-something thousand," she hovers and reads
Old Town: 240 customers/day, $53,000. The label was always in the data; hover just surfaces it on demand instead of cluttering the page. - Zoom and pan let her explore density. When ten shops pile into one corner of a scatter, she drags a box around them and the chart zooms in so she can tell them apart.
- A live map lets her drag and zoom the city itself, instead of squinting at a fixed image.
First, the data. Each lesson runs in a fresh R session, so we build Maya's six shops right here (run this once):
Everything we draw, the chart and the map, comes from this one frame. Start with the plain static chart Maya already knows how to make: customers across the bottom, revenue up the side, one point per shop. It answers the big question (busier shops do take more) but it cannot tell you which dot is which:
That p is our starting point. In the next step we make it talk back.
ggplotly(): wrap a ggplot, get hover for free
Here is the happy surprise: you do not rebuild your chart to make it interactive. You wrap the ggplot you already have. The plotly package gives you ggplotly(), which takes a finished ggplot and redraws it as an htmlwidget, a small self-contained interactive chart, built in JavaScript, that R writes into a web page. Same data, same aes() mappings, same geom; now it responds to the mouse.
ggplotly() reads the mappings you already declared. Because you mapped customers to x and revenue to y, those are exactly what shows up when you hover a point. The tooltip (the little label that appears under the cursor) is built straight from your aes():
library(plotly)
# p is the ggplot from the previous step
ggplotly(p)
That single line turns the static scatter into the interactive one pictured below. Hover any point to read the exact numbers; the in-browser R session here cannot draw a live plotly widget, so the chart-plotter below stands in for the experience, showing the same scatter, the Pearson correlation, and the ggplot code underneath:
ggplotly() is the fast path: keep building charts the ggplot way you already know, then add one wrap to ship them interactive. You learn no new chart grammar.There is also a from-scratch path, plot_ly(), which builds a plotly chart directly without a ggplot. It maps columns with a formula, the little ~column syntax that means "use this column from the data," and gives finer control over the tooltip:
library(plotly)
plot_ly(shops, x = ~customers, y = ~revenue, type = "scatter", mode = "markers",
text = ~name, hoverinfo = "text+x+y") # show the shop name on hover
Reach for ggplotly() when you already have a ggplot (almost always); reach for plot_ly() when you want hover behaviour ggplot cannot express.
What does ggplotly() do?
You spent ten minutes perfecting a ggplot called p, its colours, labels and theme. Now you want it interactive, so you call ggplotly(p). What happens to your work?
Build the chart you will make interactive
Before you can wrap a chart in ggplotly(), you need the chart. Maya wants revenue compared shop by shop, drawn as horizontal bars sorted from biggest to smallest. The aes() and coord_flip() are done; you just need the geom.
The trap: geom_bar() counts rows by default, but Maya's revenue is already a value she wants drawn as the bar's height. The geom that draws a bar from a value you supply is geom_col(). Fill in the blank.
Show answer
library(ggplot2)
ggplot(shops, aes(x = reorder(name, revenue), y = revenue)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(x = NULL, y = "revenue last month ($)")A map is just points placed by longitude and latitude
Maya's other question is where. Strip the streets away and a map is nothing more than your rows placed on a grid: longitude (how far east or west, the lon column) across the bottom, latitude (how far north or south, the lat column) up the side. You can preview that arrangement with a plotting tool you already own, a plain ggplot scatter of lon against lat:
That scatter already shows the real shape of Maya's chain, spread across town rather than strung along one road (the weak correlation just confirms the shops do not line up along a single street). What it lacks is the streets. That is the one thing a mapping package adds: it drapes these same points over a real city map you can pan and zoom.
The package for that is leaflet, and it builds a map as a short pipeline, joined by the pipe |> (which feeds the result on its left into the next function), one layer at a time, the same way ggplot stacks geoms with +:
library(leaflet)
leaflet(shops) |> # start a map from the data
addTiles() |> # lay down the street basemap
addCircleMarkers(lng = ~lon, lat = ~lat, # one circle per shop, by coordinate
popup = ~name) # click a circle to see its name
Three new pieces, each one layer of the pipeline:
leaflet(shops)starts an empty map fed by the data frame.addTiles()lays down the basemap: the street map itself, drawn from free map tiles (small square images, here from OpenStreetMap) that load and stitch together as you pan and zoom.addCircleMarkers()drops one circle marker at each row's position.lng = ~lonandlat = ~latuse the~columnformula from before to say "read longitude from theloncolumn, latitude fromlat."popup = ~namemakes each circle show its shop name when clicked.
Like plotly, a leaflet map is an htmlwidget, so it shows up in a browser or a knitted report, not in the plain console here. Run the snippet in your own R and Maya's six shops appear on a draggable city map.
Drop a marker for every shop
Here is Maya's map, missing one layer. The basemap is down; now add the layer that places one circle per shop at its coordinates, reading position from the lon and lat columns with the ~column formula. Fill in the blank with the layer that adds those markers.
Show answer
library(leaflet)
leaflet(shops) |>
addTiles() |>
addCircleMarkers(lng = ~lon, lat = ~lat, popup = ~name)When NOT to make it interactive
Maya's accountant asks for the revenue chart inside a printed PDF report that goes to the bank, the kind that gets printed on paper. Maya is tempted to drop in her shiny new ggplotly() chart. What should she actually send?
Interactive or static? Pick by destination
Interactivity is a tool, not an upgrade you always apply. The right question is never "can I make this interactive?" but "where will this chart be read, and by whom?" Match the chart to its home:
| Where it lives | Best choice | Why |
|---|---|---|
| A web page, an HTML report, a Quarto dashboard | Interactive (plotly / leaflet) | Hover and zoom work; the reader can explore |
| A printed PDF, a slide, a paper, a journal | Static ggplot | No cursor, no browser, so interactivity is lost |
| A dataset with thousands of points or markers | Static, or interactive with care | Thousands of markers can slow the browser; cluster them or summarise first |
| Anywhere accessibility matters | Never hide essential facts behind hover alone | Hover is invisible to keyboard, screen-reader and touch users |
Two more honest limits worth carrying:
- File size. An htmlwidget embeds its own JavaScript, so an interactive HTML file is heavier than a static PNG. Fine for one dashboard; think twice before putting fifty on one page.
- Performance. A leaflet map with thousands of markers, or a plotly chart with huge traces, can lag. The fix is to thin the data first: cluster nearby markers, or aggregate before you plot.
References
A few authoritative places to take this further:
- Interactive web-based data visualization with R, plotly, and shiny (the plotly-R book) - Carson Sievert's free book, the definitive guide to
ggplotly(),plot_ly()and tooltips. - plotly for R: the official getting-started reference - worked examples for every chart type, the canonical docs for the package you used here.
- leaflet for R: the official documentation - tiles, markers, popups and
~formulas, with a gallery, from the team that maintains the package. - htmlwidgets for R - the framework underneath both plotly and leaflet; read this to understand why they render to HTML, not the console.
Lesson 1 complete
You can now make a chart talk back. You saw exactly what interactivity adds (hover for the precise value and identity, zoom and pan to explore), turned a plain ggplot interactive with a single ggplotly() wrap, met the from-scratch plot_ly() and its ~column formula, and put Maya's six shops on a leaflet map with addTiles() and addCircleMarkers(). Just as important, you learned to stop and ask where will this be read?, sending an explorable chart to the web and a clean static one to print.
Next, Lesson 2: Quarto dashboards and linked views. A single interactive chart is one tile; a dashboard arranges many of them into a layout, and links them so that selecting a shop in one view filters every other view at once, the responsiveness you felt on this lesson's cover, built for real.