Interactive Plots with plotly
Reviewed & published by Brayan K
plotly turns R charts into interactive, web-based graphics — hover for tooltips, drag to zoom, click the legend to toggle series — all from familiar R syntax.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll build figures with plot_ly(), convert ggplot2 plots with ggplotly(), layer traces, and shape the figure with layout().
What You'll Learn in This Lesson
- How to build interactive charts with plot_ly()
- How to map columns with ~formula notation
- How to convert ggplot2 plots with ggplotly()
- What traces are and how to layer them
- How layout() controls titles and axes
- When interactive plotly beats static ggplot2
1️⃣ Building a Chart with plot_ly()
plot_ly() creates an interactive figure. Map columns to axes with formula notation — x = ~wt — and choose a type and mode.
library(plotly)
# plot_ly() builds an interactive figure. Map columns with ~formulas.
# The result supports hover, zoom, and pan in the browser.
fig <- plot_ly(
data = mtcars,
x = ~wt, # weight on the x axis
y = ~mpg, # miles per gallon on the y axis
type = "scatter",
mode = "markers"
)
fig
# Hover over any point to see its (wt, mpg) values in a tooltip.2️⃣ Converting ggplot2 with ggplotly()
Already built a ggplot? ggplotly() wraps it into an interactive chart in one call, adding hover tooltips, a clickable legend, and zoom while keeping your aesthetics.
library(ggplot2)
library(plotly)
# Already have a ggplot? ggplotly() makes it interactive in one call.
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
geom_point(size = 2) +
labs(title = "MPG vs Weight", color = "Cylinders")
ggplotly(p)
# Same plot, now with hover tooltips, a clickable legend, and zoom.3️⃣ Traces and layout()
A figure is made of traces — individual data series. add_trace() layers more series in, and layout() sets the title, axis labels, and legend.
library(plotly)
# Build up a figure from multiple TRACES with add_trace().
# A trace is one data series; layering them combines series in one figure.
months <- 1:6
fig <- plot_ly(x = ~months, y = ~c(10, 14, 12, 18, 20, 25),
type = "scatter", mode = "lines+markers", name = "2024") |>
add_trace(y = ~c(8, 11, 13, 15, 19, 22), name = "2023") |>
layout(title = "Monthly sales",
xaxis = list(title = "Month"),
yaxis = list(title = "Units"))
figYour turn. Fill in the ___ blank, run it, and compare with the expected output.
library(plotly)
# 🎯 YOUR TURN — replace each ___ then run it.
# 1) Make an interactive scatter of horsepower vs mpg from mtcars
fig <- plot_ly(
data = mtcars,
x = ~hp,
y = ~mpg,
type = "scatter",
mode = ___ # 👉 replace ___ with "markers"
)
# 2) Add a title with layout()
fig <- layout(fig, title = "MPG vs Horsepower")
fig
# ✅ Expected: an interactive scatter; hover a point to see hp and mpg.Mini-Challenge: Make a ggplot Interactive
Build a ggplot bar chart of mean mpg by cylinder, then convert it with ggplotly(). The one-line conversion is the fastest path from a static figure to an explorable one.
# 🎯 MINI-CHALLENGE: convert a ggplot to interactive.
#
# 1. Build a ggplot bar chart of average mpg by cylinder count
# (group mtcars by cyl, plot the means).
# 2. Give it a title with labs().
# 3. Wrap it in ggplotly() to make it interactive.
# 4. Hover a bar to confirm the tooltip shows the value.
#
# ✅ Expected: an interactive bar chart; hovering shows the mean mpg.
# your code hereCommon Errors (and the fix)
- Forgetting the ~ in mappings — plotly uses formulas: write x = ~wt, not x = wt.
- "could not find function 'plot_ly'" — load the package with library(plotly).
- Nothing appears in a script — printing happens interactively; in a saved script call print(fig) or use htmlwidgets::saveWidget().
- ggplotly() loses a custom theme element — not every ggplot2 feature translates; rebuild that part with native plotly layout().
Pro Tips
- 💡 Customize tooltips: use text = ~label and hovertemplate to control what hover shows.
- 💡 Pipe to build up figures: plot_ly(...) |> add_trace(...) |> layout(...) reads top to bottom.
- 💡 Embed in Shiny or R Markdown: plotly widgets drop straight into dashboards and HTML reports.
📋 Quick Reference — plotly
| Task | R Syntax |
|---|---|
| Interactive scatter | plot_ly(df, x = ~a, y = ~b, type = "scatter") |
| Convert a ggplot | ggplotly(p) |
| Add a series | add_trace(fig, y = ~c) |
| Title and axes | layout(fig, title = "...", xaxis = list(...)) |
| Lines mode | mode = "lines+markers" |
| Save widget | htmlwidgets::saveWidget(fig, "out.html") |
🎉 Lesson Complete!
- ✅ plot_ly() builds interactive figures with ~formula mappings
- ✅ ggplotly() converts a ggplot2 plot in one call
- ✅ Traces are data series; add_trace() layers them
- ✅ layout() sets titles, axes, and legend
- ✅ Hover, zoom, and pan make plotly ideal for exploration
Practice quiz
Which function creates an interactive plot directly in plotly?
- plot_ly()
- ggplot()
- interactive()
- draw_ly()
Answer: plot_ly(). plot_ly() is plotly's primary function for building interactive graphics in R.
What does ggplotly() do?
- Adds a regression line
- Deletes a ggplot
- Converts an existing ggplot2 plot into an interactive plotly chart
- Saves a plot to PNG
Answer: Converts an existing ggplot2 plot into an interactive plotly chart. ggplotly() takes a ggplot2 object and makes it interactive with hover and zoom.
What is the main advantage of plotly over static ggplot2?
- It uses less code
- It adds interactivity like hover, zoom, and pan
- It needs no data
- It is always faster to render
Answer: It adds interactivity like hover, zoom, and pan. plotly's defining feature is interactivity: hovering, zooming, panning, and tooltips in the browser.
In plot_ly(), what does a 'trace' represent?
- The plot title
- A data import step
- An error message
- A single layer or data series in the figure
Answer: A single layer or data series in the figure. A trace is one data series or layer; figures can combine several traces with add_trace().
How do you map data columns to axes in plot_ly()?
- With formula arguments like x = ~col and y = ~col
- By renaming the data frame
- With the aes() function only
- It is automatic and cannot be set
Answer: With formula arguments like x = ~col and y = ~col. plot_ly() uses formula notation such as x = ~wt, y = ~mpg to map columns to aesthetics.
What appears when a user hovers over a point in a plotly chart?
- The plot is deleted
- A tooltip showing the underlying values
- A new window opens
- Nothing
Answer: A tooltip showing the underlying values. Hovering reveals a tooltip with the data values for that point by default.
Which function customizes titles and axes in plotly?
- theme()
- labs()
- title()
- layout()
Answer: layout(). layout() controls the figure's title, axes, legend, and overall appearance in plotly.
Which add_* function overlays an extra series on a plot_ly figure?
- add_series()
- add_plot()
- add_trace()
- add_layer()
Answer: add_trace(). add_trace() (and helpers like add_lines/add_markers) layer additional data onto a figure.
When is static ggplot2 often preferable to plotly?
- Never
- For print, PDFs, and publications where interactivity is not useful
- Only for 3D charts
- Only for time series
Answer: For print, PDFs, and publications where interactivity is not useful. Static ggplot2 is ideal for print and PDF output where hover and zoom add nothing.
What type of output does a plotly chart render as in a browser?
- An HTML/JavaScript widget
- A CSV file
- A LaTeX document
- A plain PNG image
Answer: An HTML/JavaScript widget. plotly renders an interactive HTML widget powered by JavaScript (htmlwidgets).
Continue this course
- Previous: Cross-Validation & ROC
- Next: Parallel Computing in R — Use every core with parallel, foreach + doParallel, and the future framework
- Quick reference: R cheat sheet
Frequently asked questions
What is plotly and how is it different from ggplot2?
plotly is an R package for building interactive, web-based graphics. Where ggplot2 produces a static image, plotly produces an HTML widget (powered by JavaScript) that the viewer can hover over for tooltips, zoom into, pan, and explore in a browser or the RStudio Viewer. ggplot2 excels at polished static figures for print and PDFs; plotly excels when interactivity helps — dashboards, exploratory analysis, and Shiny apps. They are complementary, not rivals, and plotly can even render ggplot2 plots.
When should I use plot_ly() versus ggplotly()?
Use ggplotly() when you already have a ggplot2 plot you like and just want to add interactivity — it converts the existing plot in a single call, preserving most of your aesthetics. Use plot_ly() when you are building a chart from scratch and want full access to plotly's native features, formula mapping (x = ~col), and fine control over traces and layout. Many people prototype with ggplot2, convert with ggplotly() for a quick interactive view, then switch to plot_ly() when they need something ggplotly() does not translate well.
What are traces and layout in plotly?
A trace is a single data series or layer in a figure — a set of markers, a line, a set of bars. You start a figure with plot_ly() and add more series with add_trace() (or helpers like add_lines() and add_markers()), so a chart with two lines has two traces, each with its own name in the legend. layout() controls everything around the traces: the title, axis titles and ranges, the legend, margins, and hover behaviour. Together, traces define what is plotted and layout defines how the figure is framed.
How does hovering and the tooltip work?
By default, when you move the cursor over a data point plotly shows a tooltip with that point's underlying values — for a scatter, the x and y. You can customize what appears using the text argument and hovertemplate, so a tooltip can include extra columns or formatted labels instead of just raw coordinates. Hover, along with the toolbar that lets you zoom, pan, reset, and download the chart as a PNG, is what makes plotly useful for exploration: you can inspect individual records without cluttering the plot with labels.