Interactive Plots with plotly
plotly is an R package that creates interactive web-based visualizations. It works smoothly with ggplot2, allowing you to convert static charts into interactive ones with a single function call. This tutorial covers everything you need to know to create stunning interactive plots.
What you’ll learn
This tutorial covers the key concepts and practical techniques for working with Interactive Plots with plotly. By the end, you will know how to apply the core functions in real data analysis workflows.
Installation and setup
Install plotly from CRAN:
install.packages("plotly")
library(plotly)
Creating plotly charts from scratch
For full control, create plotly charts directly using plot_ly(). The syntax uses the pipe operator %>% to add layers:
plot_ly(mpg, x = ~displ, y = ~hwy, color = ~class, type = "scatter", mode = "markers") %>%
layout(title = "Fuel Efficiency by Class",
xaxis = list(title = "Engine Displacement (L)"),
yaxis = list(title = "Highway MPG"))
Interactive bar charts
Create interactive bar charts that reveal values on hover:
plot_ly(diamonds, x = ~cut, y = ~price, color = ~clarity, type = "bar") %>%
layout(barmode = "stack",
title = "Diamond Prices by Cut and Clarity",
xaxis = list(title = "Cut"),
yaxis = list(title = "Price ($)"))
Line charts and time series
For time series data, plotly handles dates automatically:
# Sample time series data
data <- data.frame(
date = seq(as.Date("2025-01-01"), by = "month", length.out = 12),
value = cumsum(rnorm(12, mean = 10, sd = 5))
)
plot_ly(data, x = ~date, y = ~value, type = "scatter", mode = "lines+markers") %>%
layout(title = "Monthly Revenue Trend",
xaxis = list(title = "Month"),
yaxis = list(title = "Revenue ($)"))
Box plots
Box plots in plotly are fully interactive, showing quartiles on hover:
plot_ly(diamonds, x = ~cut, y = ~price, color = ~cut, type = "box") %>%
layout(title = "Price Distribution by Cut Quality",
xaxis = list(title = "Cut"),
yaxis = list(title = "Price ($)"))
Heatmaps
Visualize matrix data with interactive heatmaps:
# Create correlation matrix
corr <- cor(mtcars)
plot_ly(z = corr, x = names(mtcars), y = names(mtcars), type = "heatmap") %>%
layout(title = "Car Features Correlation Matrix")
Customizing tooltips
Control exactly what appears in hover tooltips using the text aesthetic:
plot_ly(mpg, x = ~displ, y = ~hwy,
text = ~paste("Model:", model, "<br>Year:", year),
hoverinfo = "text+x+y",
type = "scatter", mode = "markers") %>%
layout(title = "Custom Hover Information")
Interactive 3D plots
plotly excels at 3D visualizations that you can rotate and explore:
plot_ly(mtcars, x = ~wt, y = ~hp, z = ~qsec, color = ~disp,
type = "scatter3d", mode = "markers") %>%
layout(title = "Car Performance in 3D")
Building interactive dashboards
Combine multiple plots into a dashboard using subplot:
# Create individual plots
p1 <- plot_ly(mpg, x = ~displ, y = ~hwy, type = "scatter", mode = "markers")
p2 <- plot_ly(mpg, x = ~class, y = ~hwy, type = "box")
p3 <- plot_ly(mpg, x = ~cyl, type = "histogram")
# Combine into dashboard
subplot(p1, p2, p3, nrows = 2)
Saving and sharing
Export your interactive plots as HTML files that can be shared and viewed in any browser:
# Save as standalone HTML
saveWidget(plot_ly(mpg, x = ~displ, y = ~hwy, type = "scatter", mode = "markers"),
file = "interactive-plot.html")
# Or embed in R Markdown / Quarto documents
Basic interactive charts
plotly::plot_ly() creates charts with the plotly JavaScript library. add_trace() appends additional data series. For users already familiar with ggplot2, ggplotly(gg) converts an existing ggplot to an interactive plotly chart with one function call, it preserves aesthetics, themes, and facets. The converted chart gains pan, zoom, and hover tooltips automatically.
Hover text customization
hovertemplate controls what appears in hover boxes. Use %{x} and %{y} for axis values, %{text} for custom text passed via the text argument, and %{customdata} for data passed via customdata. Format numbers: %{y:.2f} shows two decimal places; %{y:,} adds comma separators. End the template with <extra></extra> to remove the trace name from the hover box.
Linked charts with crosstalk
crosstalk enables linked highlighting between multiple htmlwidgets without a server. SharedData$new(df) wraps a data frame for sharing. Pass the SharedData object to plot_ly(), DT::datatable(), or other crosstalk-aware widgets, selecting rows in the table highlights points in the chart and vice versa. This works in static HTML files without Shiny, making it suitable for reports published to GitHub Pages or Posit Connect without an active R session.
Converting ggplot2 to plotly
plotly::ggplotly(p) converts most ggplot2 objects to interactive plotly charts with one function call. Hover tooltips are automatically populated from the aesthetic mappings. The tooltip argument selects which aesthetics appear in the tooltip: ggplotly(p, tooltip = c("x", "y", "colour")).
Limitations exist: some ggplot2 geoms have no direct plotly equivalent and render as static images or are dropped. geom_label_repel(), stat_ellipse(), and custom Stat extensions often do not convert cleanly. For these cases, build the plotly chart natively rather than converting.
plotly_json(p) returns the JSON representation of a plotly figure, which is useful for debugging why a conversion did not produce the expected result.
Native plotly syntax
Native plotly uses a pipe-based API. plot_ly(data, x = ~x_col, y = ~y_col, type = "scatter", mode = "markers") creates a scatter plot. The ~ syntax references data frame columns. Add traces with add_trace() or type-specific helpers like add_markers(), add_lines(), add_bars().
layout() controls chart-wide options: axis titles, legend position, margin, font. layout(p, title = "My Chart", xaxis = list(title = "X Label"), yaxis = list(title = "Y Label")).
Subplots combine multiple charts: subplot(p1, p2, nrows = 1, shareX = FALSE, shareY = FALSE). The shareX and shareY arguments link axis ranges across subplots, zooming one zooms all. This is useful for linked time series where each panel shows a different metric over the same time range.
Hover and click interactions
hovertemplate customizes the tooltip format using Plotly’s template syntax. hovertemplate = "<b>%{x}</b><br>Value: %{y:.2f}<extra></extra>" formats a hover with bold x label and two-decimal y. The <extra></extra> tag removes the secondary trace name box.
event_register(p, "plotly_click") enables click events in Shiny. In the Shiny server: event_data("plotly_click") returns the data for the clicked point, including x, y, and trace index. This powers drill-down dashboards where clicking a summary chart reveals detail for the selected item.
highlight_key() from the crosstalk package enables cross-widget filtering. Selecting points in one plotly chart highlights corresponding rows in a DT table and other linked widgets, without a Shiny server, purely client-side.
Animation
Plotly animations use the frame aesthetic to define animation frames. In ggplotly: add frame = ~year to the ggplot aesthetic mapping, then convert. In native plotly: include frame = ~year in plot_ly(). animation_opts(frame = 100, easing = "linear") controls frame duration in milliseconds and transition easing.
animation_slider() adds a playback slider. animation_button() adds a play/pause button. For dashboards, consider whether animation adds insight or just visual complexity, animated charts are harder to read than static small multiples for comparisons.
3D charts and surface plots
plot_ly(z = ~matrix_data, type = "surface") renders a 3D surface from a matrix of z values. type = "scatter3d" creates 3D scatter plots with x, y, z aesthetics. 3D charts can reveal structure in three-variable data but are harder to read than 2D projections for most purposes.
For geographic data, type = "choropleth" creates choropleth maps using GeoJSON boundaries, and type = "scattergeo" plots points on a world map. These require either ISO country/state codes or a custom GeoJSON boundary file.
Why interactive visualization
Static charts communicate a fixed view of data. Interactive charts let readers explore the data themselves, hovering to see exact values, zooming into dense regions, clicking to filter, and tracing relationships across linked plots. For exploratory analysis and data applications where the audience wants to ask their own questions, interactivity turns a chart from a conclusion into a tool.
The cost of interactivity is file size and rendering complexity. A static ggplot2 chart saved as PNG is kilobytes; an equivalent plotly chart embedded in HTML is hundreds of kilobytes because it includes the JavaScript library and all data points. For publication or print, static output is always the better choice. For web dashboards and reports where readers have browsers and bandwidth, interactivity adds genuine value.
The trace model
Plotly builds charts from traces, where each trace is a data series with a specified type, scatter, bar, box, histogram, heatmap, surface, and more. Multiple traces in one figure share axes. The layout object controls axes, titles, legends, and overall figure appearance. This trace-based model differs from ggplot2’s layer-based model: in plotly, you explicitly choose a chart type per trace, while ggplot2 infers it from the geometry.
The mode attribute on scatter traces controls whether points are drawn as markers, lines, or both. Markers give scatter plots. Lines give line charts. Text mode draws labels instead of points. Combining modes — markers and lines together — gives connected scatter plots where both the individual points and their connections are visible.
Hover customization
The default hover text shows the x and y coordinates. Customizing hover text with hovertemplate gives full control over what appears when a reader mouses over a point. The template uses variable references to include data values, and supports HTML formatting for multi-line tooltips. A well-designed hover template answers the reader’s likely questions — what is the value, what does this point represent, what is the context — without overwhelming them with every available data field.
Setting hovermode on the layout controls whether hover shows information for the nearest single point or for all traces at the same x position simultaneously. The “x unified” mode draws a single tooltip listing all trace values at the hovered x coordinate, which is useful for time series comparisons where multiple lines share the same time axis.
Summary
plotly transforms your R visualizations into interactive experiences. Key functions to remember:
ggplotly(), Convert ggplot2 charts instantlyplot_ly(), Create plots from scratchsubplot(), Combine multiple plotssaveWidget(), Export as standalone HTML
With these tools, you can create compelling interactive visualizations that engage your audience and reveal data insights dynamically.
Next steps
Now that you understand interactive plots with plotly, explore these related topics to deepen your knowledge and apply these techniques in more complex scenarios.