r-plotly - Expert Interactive Data Visualization in R
Master interactive, web-based data visualization in R using plotly for exploratory analysis, dashboards, animations, and production applications.
Overview
Plotly is R's leading framework for interactive, web-based visualizations. Unlike static plotting libraries, every plotly graphic is interactive by default - users can hover for details, zoom, pan, select data, and trigger events. This skill provides comprehensive guidance on creating publication-quality interactive visualizations, from basic charts to complex animated dashboards integrated with Shiny.
Key Capabilities:
- Interactive plots with hover, zoom, pan, click, and brush
- Frame-based animations for temporal narratives
- 3D visualizations with camera controls
- Geographic maps with projections and tooltips
- Shiny integration with event handling
- Web-ready HTML widgets for embedding
- Performance optimization for large datasets
When to use plotly vs ggplot2:
- Use plotly when: Need interactivity, animations, 3D plots, web deployment, Shiny dashboards, or exploring data dynamically
- Use ggplot2 when: Creating publication-ready static plots, need fine-grained aesthetic control, or working with print media
- Use both: Convert ggplot2 to interactive with
ggplotly()for quick interactivity layer
For static plot design principles, see the ggplot2 skill. For general Shiny concepts, see the r-shiny skill.
The Three-Layer Architecture
Every plotly figure follows a declarative three-layer model:
Data → Traces (what to draw) → Layout (how it looks) → Config (interactivity options)
Layer 1: Traces (What to Draw)
Traces define what gets drawn - the geometric representation of data.
# Scatter trace with markers
plot_ly(data, x = ~x_var, y = ~y_var, type = "scatter", mode = "markers")
# Multiple traces on one plot
plot_ly(data) |>
add_trace(x = ~x, y = ~y1, type = "scatter", mode = "lines", name = "Series 1") |>
add_trace(x = ~x, y = ~y2, type = "scatter", mode = "lines", name = "Series 2")
Key trace parameters:
type: Chart type ("scatter", "bar", "box", "heatmap", "surface", etc.)mode: For scatter traces ("markers", "lines", "markers+lines", "text")name: Legend label- Data aesthetics:
x,y,z,color,size,symbol,text
Layer 2: Layout (How It Looks)
Layout controls everything that isn't data: titles, axes, colors, spacing, legends.
plot_ly(data, x = ~x, y = ~y) |>
layout(
title = "Main Title",
xaxis = list(title = "X Label", gridcolor = "lightgray"),
yaxis = list(title = "Y Label", gridcolor = "lightgray"),
legend = list(x = 0.7, y = 0.95),
plot_bgcolor = "white",
paper_bgcolor = "#f5f5f5"
)
Key layout parameters:
title,xaxis,yaxis: Labels and axis configurationlegend: Position and stylingmargin: Plot spacingannotations: Text and shapes overlaytemplate: Built-in themes ("plotly", "ggplot2", "seaborn", "simple_white")
Layer 3: Config (Interactivity Options)
Config controls interactive features and the modebar (toolbar).
plot_ly(data, x = ~x, y = ~y) |>
config(
displaylogo = FALSE, # Hide plotly logo
modeBarButtonsToRemove = c("pan2d", "lasso2d"),
toImageButtonOptions = list( # Download button options
format = "png",
filename = "my_plot",
width = 800,
height = 600
)
)
Philosophy: This separation allows independent modification of data representation (traces), appearance (layout), and interaction (config) without affecting each other.
Core Functions & Workflow
1. plot_ly() - Initialize Plot
Starts a plotly figure and optionally adds the first trace.
# Basic initialization
p <- plot_ly(data, x = ~var1, y = ~var2, type = "scatter", mode = "markers")
# With aesthetics
p <- plot_ly(data, x = ~x, y = ~y, color = ~group, size = ~value,
type = "scatter", mode = "markers")
# Minimal (add traces later)
p <- plot_ly(data)
Key parameters:
data: Data frame (required)x,y,z: Column mappings using~variablesyntaxtype: Chart type (defaults to "scatter")color,size,symbol: Aesthetic mappings- All trace-level parameters accepted
2. add_trace() - Layer Additional Traces
Adds layers to existing plot, enabling multi-trace visualizations.
plot_ly(data, x = ~x) |>
add_trace(y = ~y1, type = "scatter", mode = "lines", name = "Actual") |>
add_trace(y = ~y2, type = "scatter", mode = "lines", name = "Predicted",
line = list(dash = "dash"))
Convenience functions (wrappers around add_trace):
add_markers(),add_lines(),add_paths(),add_segments()add_bars(),add_histogram(),add_boxplot()add_heatmap(),add_surface(),add_polygons()
3. layout() - Configure Appearance
Modifies plot layout - titles, axes, legends, annotations, themes.
p |>
layout(
title = list(text = "Interactive Plot", font = list(size = 18)),
xaxis = list(title = "Time", type = "date", tickformat = "%b %Y"),
yaxis = list(title = "Value", range = c(0, 100)),
hovermode = "x unified",
showlegend = TRUE,
legend = list(x = 1, xanchor = "right", y = 1)
)
4. config() - Set Interactivity
Controls modebar, responsiveness, and interaction settings.
p |>
config(
responsive = TRUE, # Auto-resize
displayModeBar = TRUE, # Show toolbar
scrollZoom = FALSE, # Disable scroll-to-zoom
modeBarButtonsToRemove = c("select2d", "lasso2d")
)
Complete Example
library(plotly)
library(dplyr)
# Data preparation
data <- mtcars |>
mutate(cyl = as.factor(cyl))
# Build plot with three layers
data |>
plot_ly(x = ~wt, y = ~mpg, color = ~cyl, size = ~hp,
text = ~paste("Car:", row.names(mtcars)),
type = "scatter", mode = "markers") |>
layout(
title = "Fuel Efficiency vs Weight",
xaxis = list(title = "Weight (1000 lbs)"),
yaxis = list(title = "Miles per Gallon"),
hovermode = "closest"
) |>
config(displaylogo = FALSE)
Quick Chart Type Selection Guide
Choose chart type based on your analytical goal:
Relationships & Correlation
- Scatter plot:
plot_ly(x = ~x, y = ~y, type = "scatter", mode = "markers") - Line plot:
plot_ly(x = ~x, y = ~y, type = "scatter", mode = "lines") - Bubble chart: Add
size = ~variableto scatter - 3D scatter:
plot_ly(x = ~x, y = ~y, z = ~z, type = "scatter3d")
Distributions
- Histogram:
plot_ly(x = ~var, type = "histogram") - Box plot:
plot_ly(y = ~var, type = "box") - Violin plot:
plot_ly(y = ~var, type = "violin") - 2D histogram:
plot_ly(x = ~x, y = ~y, type = "histogram2d")
Comparisons
- Bar chart:
plot_ly(x = ~category, y = ~value, type = "bar") - Grouped bars:
plot_ly(x = ~cat, y = ~val, color = ~group, type = "bar")+layout(barmode = "group") - Stacked bars: Same as grouped +
layout(barmode = "stack")
Compositions
- Pie chart:
plot_ly(labels = ~category, values = ~count, type = "pie") - Sunburst:
plot_ly(labels = ~labels, parents = ~parents, values = ~values, type = "sunburst") - Treemap:
plot_ly(labels = ~labels, parents = ~parents, values = ~values, type = "treemap")
Geographic
- Choropleth:
plot_ly(locations = ~state, z = ~value, type = "choropleth") - Scatter map:
plot_ly(lat = ~lat, lon = ~lon, type = "scattergeo") - Mapbox:
plot_ly(lat = ~lat, lon = ~lon, type = "scattermapbox")
Statistical
- Heatmap:
plot_ly(z = ~matrix, type = "heatmap") - Contour:
plot_ly(z = ~matrix, type = "contour") - 3D surface:
plot_ly(z = ~matrix, type = "surface")
For complete chart type reference with all parameters: references/chart-types-reference.md
Interactivity Patterns Quick Reference
Hover Information
Default behavior: Automatic tooltips with x, y, and trace name.
Custom hover text:
plot_ly(data, x = ~x, y = ~y, text = ~paste("Value:", y, "<br>Category:", cat))
Hover template (formatted tooltips):
plot_ly(data, x = ~x, y = ~y,
hovertemplate = "<b>%{text}</b><br>X: %{x:.2f}<br>Y: %{y:.2f}<extra></extra>",
text = ~label)
Zoom and Pan
Constraints:
layout(
xaxis = list(fixedrange = TRUE), # Disable x-axis zoom/pan
yaxis = list(range = c(0, 100)) # Fixed y range
)
Reset view: Modebar "Reset axes" button (default enabled)
Click Events (Shiny Only)
# In Shiny server
observeEvent(event_data("plotly_click"), {
clicked_point <- event_data("plotly_click")
# Access: clicked_point$x, clicked_point$y, clicked_point$curveNumber
})
Selection
Box select / Lasso select:
plot_ly(x = ~x, y = ~y, type = "scatter", mode = "markers") |>
layout(dragmode = "lasso") # or "select" for box
Animation
Frame-based animation:
plot_ly(data, x = ~x, y = ~y, frame = ~time_var, type = "scatter", mode = "markers") |>
animation_opts(frame = 500, transition = 300)
For complete interactivity patterns: references/interactivity-reference.md
ggplotly() Conversion
Convert ggplot2 graphics to interactive plotly with ggplotly().
Basic Conversion
library(ggplot2)
library(plotly)
# Create ggplot
p <- ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) +
geom_point(size = 3) +
theme_minimal()
# Convert to interactive
ggplotly(p)
When to Convert vs Build Natively
Use ggplotly() when:
- ✅ Already have ggplot2 plots to make interactive
- ✅ Need quick interactivity layer with minimal effort
- ✅ Prefer ggplot2's grammar for building plots
- ✅ Working with ggplot2 extensions (gganimate not supported, but ggplotly works)
Use plot_ly() when:
- ✅ Need custom interactivity (hover templates, click events, animations)
- ✅ Want 3D plots or advanced chart types (sunburst, sankey)
- ✅ Optimizing performance for large datasets
- ✅ Integrating with Shiny (more control with native plotly)
- ✅ Need features not available in ggplot2 (3D, certain map types)
Customizing Converted Plots
# Convert then customize
p_interactive <- ggplotly(p)
p_interactive |>
layout(
title = "Interactive Version",
hovermode = "x unified"
) |>
config(displaylogo = FALSE)
Limitations
Some ggplot2 features don't convert perfectly:
- Complex annotations may not transfer
- Custom geoms might not have plotly equivalents
- Faceting converts but loses some polish
- Theme elements may not match exactly
Workaround: Build core plot in ggplot2, convert, then polish with plotly's layout/config.
Common Patterns
Grouping and Coloring
Discrete groups:
plot_ly(data, x = ~x, y = ~y, color = ~group, colors = "Set1", type = "scatter")
Continuous color scale:
plot_ly(data, x = ~x, y = ~y, z = ~value, color = ~value,
type = "scatter", mode = "markers",
marker = list(colorscale = "Viridis", showscale = TRUE))
Multiple Traces on One Plot
plot_ly(data) |>
add_trace(x = ~date, y = ~sales, type = "scatter", mode = "lines", name = "Sales") |>
add_trace(x = ~date, y = ~target, type = "scatter", mode = "lines",
name = "Target", line = list(dash = "dash"))
Subplots and Faceting
Shared axes:
library(plotly)
p1 <- plot_ly(data, x = ~x, y = ~y1, type = "scatter", mode = "lines")
p2 <- plot_ly(data, x = ~x, y = ~y2, type = "scatter", mode = "lines")
subplot(p1, p2, nrows = 2, shareX = TRUE)
Grid layout:
subplot(
plot1, plot2, plot3, plot4,
nrows = 2, margin = 0.05,
shareX = FALSE, shareY = FALSE
)
Theming
Built-in templates:
plot_ly(data, x = ~x, y = ~y) |>
layout(template = "plotly_dark") # or "ggplot2", "seaborn", "simple_white"
Custom theme:
my_theme <- list(
plot_bgcolor = "#f5f5f5",
paper_bgcolor = "white",
font = list(family = "Arial", size = 12),
xaxis = list(gridcolor = "white"),
yaxis = list(gridcolor = "white")
)
plot_ly(data, x = ~x, y = ~y) |>
layout(my_theme)
Handling Dates and Time Series
plot_ly(data, x = ~date_var, y = ~value, type = "scatter", mode = "lines") |>
layout(
xaxis = list(
type = "date",
tickformat = "%b %Y",
dtick = "M3" # Tick every 3 months
)
)
Shiny Integration Basics
Plotly integrates seamlessly with Shiny for reactive dashboards.
Basic Integration
UI:
library(shiny)
library(plotly)
ui <- fluidPage(
selectInput("var", "Variable", choices = c("mpg", "hp", "wt")),
plotlyOutput("plot", height = "500px")
)
Server:
server <- function(input, output, session) {
output$plot <- renderPlotly({
plot_ly(mtcars, x = ~wt, y = ~get(input$var), type = "scatter", mode = "markers") |>
layout(yaxis = list(title = input$var))
})
}
Event Handling
Click events:
observeEvent(event_data("plotly_click"), {
d <- event_data("plotly_click")
showModal(modalDialog(
title = "Clicked Point",
paste("X:", d$x, "Y:", d$y)
))
})
Selection events:
observeEvent(event_data("plotly_selected"), {
selected <- event_data("plotly_selected")
# Access selected$x, selected$y, selected$curveNumber
# Filter data based on selection
})
For complete Shiny patterns: references/shiny-integration-reference.md
Common Mistakes to Avoid
1. Data Format Issues
❌ Wrong: Wide format when long format needed
# Won't work for grouped plots
wide_data <- data.frame(x = 1:5, series1 = rnorm(5), series2 = rnorm(5))
plot_ly(wide_data, x = ~x, y = ~series1, color = ~series2) # Wrong!
✅ Right: Long format for grouping
library(tidyr)
long_data <- pivot_longer(wide_data, cols = c(series1, series2),
names_to = "series", values_to = "value")
plot_ly(long_data, x = ~x, y = ~value, color = ~series)
2. Trace vs Layout Confusion
❌ Wrong: Putting layout properties in trace
plot_ly(x = ~x, y = ~y, title = "My Plot") # title is layout property!
✅ Right: Separate traces from layout
plot_ly(x = ~x, y = ~y) |> layout(title = "My Plot")
3. Performance with Large Data
❌ Wrong: Plotting 100k+ points without aggregation
plot_ly(huge_data, x = ~x, y = ~y, type = "scatter", mode = "markers") # Slow!
✅ Right: Aggregate or use WebGL
# Option 1: Aggregate
summary_data <- huge_data |> group_by(bin = cut(x, 100)) |> summarize(y = mean(y))
plot_ly(summary_data, x = ~bin, y = ~y)
# Option 2: WebGL for large scatter
plot_ly(huge_data, x = ~x, y = ~y, type = "scattergl", mode = "markers")
4. Browser Compatibility
Some features require modern browsers:
- WebGL (for large datasets)
- CSS animations
- SVG rendering
Always test in target deployment environment.
Resource Navigation
Reference Documentation (Deep Dives)
- Chart Types: references/chart-types-reference.md - Complete catalog of all chart types with parameters
- Layout & Styling: references/layout-styling-reference.md - Axes, legends, annotations, themes, colors
- Interactivity: references/interactivity-reference.md - Hover, click, zoom, brush, animation, linking
- Animation: references/animation-reference.md - Frame-based animations, controls, advanced patterns
- Shiny Integration: references/shiny-integration-reference.md - Events, proxy, dashboards, performance
- Best Practices: references/best-practices.md - Design principles, performance, accessibility, export
Examples (Working Code)
- Basic Plots Gallery: examples/basic-plots-gallery.md - 30+ complete, runnable examples
- Interactive Features: examples/interactive-features-examples.md - Hover, animation, linking, events
- Advanced Applications: examples/advanced-applications.md - Financial, geographic, scientific, ML dashboards
Templates (Copy-Paste Ready)
- Plot Templates: templates/plot-templates.md - 30+ templates with placeholders (DATA, X_VAR, etc.)
External Resources
- Primary book: Interactive Web-Based Data Visualization with R, plotly, and shiny by Carson Sievert
- Official docs: Plotly R Documentation
- Gallery: Plotly R Figure Reference
Workflow Guidance
When user asks about plotly visualizations:
- Understand the goal: What pattern/relationship to show? What interactivity is needed?
- Choose chart type: Match data structure and analytical goal to appropriate type
- Build with three layers:
- Traces: Data mapping and chart type
- Layout: Titles, axes, styling
- Config: Interaction controls
- Add interactivity: Hover templates, click events, animations as needed
- Optimize: Aggregate large data, use WebGL, restrict tools if needed
- Integrate: Add to Shiny if reactive behavior needed
- Test: Verify interactions work in target environment
Always provide complete, runnable code examples with proper library calls and best practices applied.