Interactive ggplot with ggiraph
Interactive ggplot with ggiraph
Add tooltips, hover effects, and click selection to your ggplot2 plots with ggiraph — an htmlwidget that makes static R graphics interactive and exportable as self-contained HTML.
What is ggiraph?
ggiraph is an R package by David Gohel that converts ggplot2 plots into interactive HTML/SVG graphics. Because it is built as an htmlwidget, it works in R Markdown documents, Shiny apps, and standalone HTML files without extra configuration.
Install from CRAN:
install.packages("ggiraph")
Load it alongside ggplot2:
library(ggplot2)
library(ggiraph)
The pattern is straightforward: use geom_X_interactive() variants instead of regular ggplot2 geoms, add tooltip and data_id aesthetics, then pass everything to girafe() to render.
Your First Interactive Plot
Here is a minimal example with the mtcars dataset:
dataset <- mtcars
dataset$carname <- row.names(mtcars)
p <- ggplot(
data = dataset,
mapping = aes(
x = wt,
y = qsec,
tooltip = carname,
data_id = carname
)
) +
geom_point_interactive(size = 3) +
labs(x = "Weight (1000 lbs)", y = "Quarter mile time (s)")
x <- girafe(ggobj = p)
x
Hover over any point to see the car name. The data_id aesthetic is also set — it enables CSS-based hover effects and, in Shiny, the selection reactive.
The three-step pattern for any interactive plot:
- Add
tooltipanddata_idaesthetics insideaes() - Use
geom_X_interactive()instead ofgeom_X() - Wrap the plot with
girafe()
Building Multi-Line Tooltips
The tooltip aesthetic accepts a character string. Use paste() to combine multiple data columns:
# Prepare the dataset
dataset <- mtcars
dataset$carname <- row.names(mtcars)
dataset$label <- paste0(
dataset$carname, "\n",
"Weight: ", round(dataset$wt, 2), " | ",
"Qsec: ", round(dataset$qsec, 2)
)
p <- ggplot(dataset, aes(wt, qsec, tooltip = label, data_id = carname)) +
geom_point_interactive(size = 3)
girafe(ggobj = p)
The tooltip now shows the car name, weight, and quarter-mile time on three lines. Note that paste0() coerces numbers to character automatically — no as.character() call needed for numeric columns.
Bar Charts with geom_bar_interactive
Bar charts follow the same pattern. Remember to set stat = "identity" when mapping y to a column directly:
dat <- data.frame(
name = c("Guy", "Ginette", "David", "Cedric", "Frederic"),
height = c(169, 160, 171, 172, 171)
)
p <- ggplot(dat, aes(x = name, y = height, fill = name, data_id = name)) +
geom_bar_interactive(stat = "identity") +
labs(x = NULL, y = "Height (cm)") +
theme_minimal() +
theme(legend.position = "none")
girafe(ggobj = p)
Customising Hover and Selection CSS
With data_id set, you can style elements on hover and click using CSS passed to opts_hover() and opts_selection():
hover_css <- "fill: #ffe7a6; fill-opacity: 0.8; cursor: pointer;"
selected_css <- "fill: #ff6b6b; stroke: #333; stroke-width: 2px;"
p <- ggplot(mtcars, aes(wt, mpg, tooltip = rownames(mtcars), data_id = rownames(mtcars))) +
geom_point_interactive(size = 3) +
theme_minimal()
x <- girafe(ggobj = p) %>%
girafe_options(
opts_hover(css = hover_css),
opts_selection(css = selected_css)
)
x
Without data_id, the hover and selection CSS has no elements to target — the browser needs a unique identifier for each geom element to apply the styles.
You can also construct CSS strings programmatically with girafe_css(). This is most useful in Shiny apps where you want to generate styles from server-side data:
my_css <- girafe_css(
text = "font-style: italic;",
id = "none"
)
# Returns: "text{fill:inherit;stroke:inherit;font-style:italic;} "
Styling the Tooltip Box
Use opts_tooltip() to control the tooltip container’s appearance:
tooltip_css <- "background-color: #1a1a2e; color: #fff;
padding: 6px; border-radius: 4px; font-size: 12px;"
x <- girafe(ggobj = p) %>%
girafe_options(
opts_tooltip(css = tooltip_css, use_fill = TRUE)
)
x
Setting use_fill = TRUE applies the background-color from your CSS to the fill rather than the stroke. The tooltip box offsets from the cursor are configurable but default to sensible values.
Controlling Selection Behaviour
The opts_selection() function controls what happens when a user clicks an element:
x <- girafe(ggobj = p) %>%
girafe_options(
opts_selection(
type = "single", # "single" or "multiple"
only_shiny = FALSE, # TRUE restricts selection to Shiny only
css = "fill: #ff6b6b;"
)
)
x
type = "single"— only one element selected at a timetype = "multiple"— Ctrl/Cmd+click to select severalonly_shiny = FALSE— lets you preview the selection highlight in a plain HTML file, not just in Shiny
Sizing the Output with opts_sizing
ggiraph renders SVG graphics. By default it rescales the output to fit the container. Control this with opts_sizing():
x <- girafe(ggobj = p, width_svg = 10, height_svg = 6) %>%
girafe_options(
opts_sizing(rescale = TRUE, width = 0.9)
)
x
width_svgandheight_svgingirafe()set the SVG canvas dimensions in inchesopts_sizing(rescale = TRUE)scales that SVG to fit the containeropts_sizing(rescale = FALSE)displays it at exact pixel size
Using ggiraph with Shiny
Selection is most powerful in Shiny, where the selected data_id values feed into reactive expressions:
# ui.R
girafeOutput("scatter_plot")
# server.R
output$scatter_plot <- renderGirafe({
girafe(
ggobj = ggplot(mtcars, aes(wt, mpg, tooltip = rownames(mtcars), data_id = rownames(mtcars))) +
geom_point_interactive(size = 3),
options = list(
opts_selection(type = "single", only_shiny = TRUE)
)
)
})
With only_shiny = TRUE, the selection is stored as a Shiny reactive value accessible via input$scatter_plot_selected. You can use this to filter data tables, update other outputs, or drive conditional logic elsewhere in the app.
Available Interactive Geoms
Most ggplot2 geoms have an interactive counterpart. Key ones:
| ggplot2 geom | ggiraph geom |
|---|---|
geom_point() | geom_point_interactive() |
geom_line() | geom_line_interactive() |
geom_area() | geom_area_interactive() |
geom_bar() | geom_bar_interactive() |
geom_label() | geom_label_interactive() |
geom_text() | geom_text_interactive() |
geom_smooth() | geom_smooth_interactive() |
geom_ribbon() | geom_ribbon_interactive() |
geom_tile() | geom_tile_interactive() |
geom_sf() | geom_sf_interactive() |
If a geom has an *_interactive() version documented at davidgohel.github.io/ggiraph, it is supported.
Common Pitfalls
No tooltip appearing. You forgot to wrap the plot with girafe(). Without it, the graphic renders as a static ggplot2 image — no interactivity is added.
Hover CSS not having any effect. The data_id aesthetic is required for CSS-based hover and selection effects. Without it, the browser cannot identify individual SVG elements.
Tooltip showing NA. The tooltip aesthetic must be character. Wrap numeric or factor values with as.character() or paste().
Plot renders at the wrong size. Adjust width_svg and height_svg in girafe(), then control scaling with opts_sizing().
See Also
- ggplot2 Extensions — other packages that extend ggplot2
- dplyr filter() — subset your data before visualising it
- Data frames — the underlying structure behind ggplot2 graphics
ggiraph is maintained by David Gohel. The source is on GitHub and the full documentation is at davidgohel.github.io/ggiraph.