The ggiraph
package in R is an
extension of the ggplot2
package, designed to simplify the process of creating
interactive and dynamic graphs. It’s
an htmlwidget, which means it’s highly compatible with RMarkdown/Quarto
document and Shiny application.
This post showcases the
key features of ggiraph
and provides a set
of graph examples using the package.
{ggiraph}
The ggiraph
package in R is an extension of the ggplot2
package, designed to simplify the process of creating
interactive charts.
Try to hover over the chart below:
It offers a set of function to add interactivity
such as geom_point_interactive()
for an interactive version
of geom_point()
, geom_sf_interactive()
for an
interactive version of geom_sf()
etc.
✍️ author → David Gohel
📘 documentation → github
⭐️ more than 500 stars on github
To get started with ggiraph
, you can install it directly
from CRAN using the install.packages
function:
Here’s an example using the geom_point_interactive()
function that “replaces” the original geom_point()
from ggplot2. It will also draw circles on
the chart, but those circle will be interactive.
This geom has some new arguments like
hover_nearest = TRUE
that makes sure we always consider the
nearest point to be hovered.
library(ggiraph)
library(tidyverse)
mtcars_db <- rownames_to_column(mtcars, var = "carname")
myplot <- ggplot(
data = mtcars_db,
mapping = aes(
x = disp, y = qsec,
# here we add interactive aesthetics
tooltip = carname, data_id = carname
)
) +
geom_point_interactive(
size = 3, hover_nearest = TRUE
)
interactive_plot <- girafe(ggobj = myplot)
htmltools::save_html(interactive_plot, "../HtmlWidget/ggiraph-2.html")
Note: the tooltip
property of the
aes
function is used to control the information to be
displayed in the tooltip.
ggiraph
becomes very powerful when multiple
plots are displayed in the same charts. Due to its efficient
mapping, we can easily create “connected” plots with
cool hover effects!
Note: check the dedicated post on how to combine plots in ggiraph to learn more about it.
library(ggiraph)
library(tidyverse)
library(patchwork)
mtcars_db <- rownames_to_column(mtcars, var = "carname")
# First plot: Scatter plot
scatter <- ggplot(
data = mtcars_db,
mapping = aes(
x = disp, y = qsec,
tooltip = carname, data_id = carname
)
) +
geom_point_interactive(
size = 3, hover_nearest = TRUE
) +
labs(
title = "Displacement vs Quarter Mile",
x = "Displacement", y = "Quarter Mile"
) +
theme_bw()
# Second plot: Bar plot
bar <- ggplot(
data = mtcars_db,
mapping = aes(
x = reorder(carname, mpg), y = mpg,
tooltip = paste("Car:", carname, "<br>MPG:", mpg),
data_id = carname
)
) +
geom_col_interactive(fill = "skyblue") +
coord_flip() +
labs(
title = "Miles per Gallon by Car",
x = "Car", y = "Miles per Gallon"
) +
theme_bw()
# Combine the plots using patchwork
combined_plot <- scatter + bar +
plot_layout(ncol = 2)
# Create a single interactive plot with both subplots
interactive_plot <- girafe(ggobj = combined_plot)
# Set options for the interactive plot
interactive_plot <- girafe_options(
interactive_plot,
opts_hover(css = "fill:cyan;stroke:black;cursor:pointer;"),
opts_selection(type = "single", css = "fill:red;stroke:black;")
)
htmltools::save_html(interactive_plot, "../HtmlWidget/ggiraph-3.html")
It is even possible to create more complex examples by adding, for example, a map:
Although CSS
is primarily used for styling web
pages, it can also be relevant in customizing visuals in
R, especially when dealing with web-based outputs like
interactive charts or Shiny
applications.
CSS
code has the following structure: a property named
(such as font-size
), a colon (:
), and the
value of that property (14px
). Each element is then
separated by a ;
.
Note: If you haven’t already, check the posts on how to use CSS in ggiraph and how to combine multiple plots and use CSS in ggiraph.
In this example, we’ll use the girafe_options()
function
and pass it several arguments:
opts_hover()
for passing the CSS when
hoveringopts_tooltip()
for passing the CSS of the
tooltiplibrary(ggiraph)
library(tidyverse)
library(ggplot2)
library(hrbrthemes)
library(viridis)
library(scales)
# Prepare data
mtcars_db <- rownames_to_column(mtcars, var = "carname") %>%
mutate(efficiency = ifelse(mpg > mean(mpg), "Above Average", "Below Average"))
# Create the plot
barplot <- ggplot(
data = mtcars_db,
mapping = aes(
x = reorder(carname, mpg),
y = mpg,
tooltip = paste(
"<strong>Car:</strong>", carname,
"<br><strong>MPG:</strong>", round(mpg, 1),
"<br><strong>Horsepower:</strong>", hp,
"<br><strong>Weight:</strong>", wt, "tons"
),
data_id = carname,
fill = mpg,
color = efficiency
)
) +
geom_col_interactive(width = 0.7) +
coord_flip() +
scale_fill_viridis(option = "C") +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
labs(title = "Miles per Gallon by Car") +
theme_ipsum(base_size = 14, plot_title_size = 22) +
theme(
legend.position = "none",
panel.grid.major.y = element_blank(),
axis.text.y = element_text(size = 10),
panel.background = element_rect(fill = "white")
)
# Create interactive plot
interactive_plot <- girafe(
ggobj = barplot,
width_svg = 10,
height_svg = 8,
options = list(
opts_hover(css = "fill:#6f1d1b;"),
opts_hover_inv(css = "opacity:0.3;"),
opts_selection(type = "multiple", css = "fill:#FF851B;stroke:black;"),
opts_toolbar(saveaspng = FALSE, position = "topright", delay_mouseout = 10000),
opts_tooltip(
css = "background-color:black;color:white;padding:30px;border-radius:10px;box-shadow:10px 10px 10px rgba(0,0,0,0.3);font-family:Arial;font-size:20px;",
opacity = 0.9,
use_fill = TRUE
),
opts_sizing(rescale = TRUE),
opts_zoom(max = 2)
)
)
# Save the plot
htmltools::save_html(interactive_plot, "../HtmlWidget/ggiraph-4.html")
onclick
effects with JavaScriptUsing the onclick
argument, you can define
actions to be performed when the user
clicks on certain parameters using JavaScript (JS).
Note: If you haven’t already, check the post on how to use JavaScript in ggiraph.
In the following example, we use JS to make the rectangles change colour and display confetti when the user clicks.
Note: check the post on how to use Javascript in ggiraph.
library(ggplot2)
library(ggiraph)
library(htmltools)
my_text <- "the R-Graph-Gallery"
p1 <- ggplot(
data = diamonds,
mapping = aes(x = color, fill = cut, data_id = cut)
) +
geom_bar_interactive(
aes(tooltip = sprintf("%s: %.0f", fill, after_stat(count))),
size = 10,
`data-blah` = my_text,
extra_interactive_params = c("data-blah"),
onclick = paste0(
"this.style.fill = this.style.fill === \"red\" ? this.getAttribute(\"original-fill\") : \"red\";",
"this.classList.toggle(\"highlighted\");",
"var tooltip = document.getElementById(\"custom-tooltip\");",
"tooltip.innerHTML = \"A chart by: \" + this.getAttribute(\"data-blah\");",
"tooltip.style.display = \"block\";",
"tooltip.style.left = (event.pageX + 10) + \"px\";",
"tooltip.style.top = (event.pageY + 10) + \"px\";",
"setTimeout(function() { tooltip.style.display = \"none\"; }, 1500);",
"confetti({
particleCount: 1000,
spread: 70,
origin: { y: 0.6 }
});"
)
) +
theme_classic()
interactive_plot <- girafe(ggobj = p1)
html_content <- htmltools::tags$html(
htmltools::tags$head(
htmltools::tags$script(src = "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"),
htmltools::tags$style("
.highlighted {
stroke: black;
stroke-width: 2px;
}
#custom-tooltip {
position: absolute;
background: white;
border: 1px solid black;
padding: 5px;
display: none;
pointer-events: none;
}
")
),
htmltools::tags$body(
interactive_plot,
htmltools::tags$div(id = "custom-tooltip")
)
)
htmltools::save_html(html_content, "../HtmlWidget/ggiraph-5.html")
Interactivity is a very interesting feature, especially for choropleth maps, as it is very intuitive to fly over a specific region and want to know more.
In the following example, we see how to use ggiraph
with
sf to create an interactive choropleth map with
additional information:
library(ggplot2)
library(ggthemes)
library(sf)
library(ggiraph)
library(dplyr)
atlas <- readr::read_rds(
"https://github.com/viniciusoike/restateinsight/raw/main/static/data/atlas_sp_hdi.rds"
)
pop_hdi <- atlas |>
st_drop_geometry() |>
mutate(
group_hdi = findInterval(HDI, seq(0.65, 0.95, 0.05), left.open = FALSE),
group_hdi = factor(group_hdi)
) |>
group_by(group_hdi) |>
summarise(score = sum(pop, na.rm = TRUE)) |>
ungroup() |>
mutate(share = score / sum(score) * 100) |>
na.omit() |>
mutate(
y_text = if_else(group_hdi %in% c(0, 7), share + 3, share - 3),
label = paste0(round(share, 1), "%"),
data_id = as.character(group_hdi) # Add data_id to pop_hdi
)
atlas <- atlas |>
mutate(group_hdi = findInterval(HDI, seq(0.65, 0.95, 0.05), left.open = FALSE))
pmap <- ggplot(atlas) +
geom_sf_interactive(aes(fill = HDI, data_id = group_hdi, tooltip = paste("HDI:", HDI)), lwd = 0.05, color = "white") +
scale_fill_fermenter(
name = "",
breaks = seq(0.65, 0.95, 0.05),
direction = 1,
palette = "YlGnBu"
) +
labs(
title = "HDI in Sao Paulo, BR (2010)",
subtitle = "Microregion HDI in Sao Paulo",
caption = "Source: Atlas Brasil"
) +
theme_map() +
theme(
legend.position = "none",
plot.title = element_text(size = 16, hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5)
)
x_labels <- c(
"0.650 or less", "0.650 to 0.699", "0.700 to 0.749", "0.750 to 0.799",
"0.800 to 0.849", "0.850 to 0.899", "0.900 to 0.949", "0.950 or more"
)
pcol <- ggplot(pop_hdi, aes(group_hdi, share, fill = group_hdi)) +
geom_col_interactive(aes(data_id = data_id, tooltip = paste("Share:", label))) +
geom_hline(yintercept = 0) +
geom_text_interactive(
aes(y = y_text, label = label, color = group_hdi, data_id = data_id),
size = 2
) +
coord_flip() +
scale_x_discrete(labels = x_labels) +
scale_fill_brewer(palette = "YlGnBu") +
scale_color_manual(values = c(rep("black", 5), rep("white", 2), "black")) +
guides(fill = "none", color = "none") +
labs(
title = "",
x = NULL,
y = NULL
) +
theme_void() +
theme(
panel.grid = element_blank(),
plot.title = element_text(size = 8), # Reduced title size
axis.text.y = element_text(size = 5), # Reduced y-axis text size
axis.text.x = element_blank(),
aspect.ratio = 1.5
)
p_hdi_atlas <- pmap + pcol + plot_layout(widths = c(3, 1))
p_hdi_atlas <- pmap + inset_element(pcol, left = 0.5, bottom = 0, right = 1, top = 0.5)
interactive_plot <- girafe(
ggobj = p_hdi_atlas,
options = list(
opts_hover(css = "fill:orange;"),
opts_hover_inv(css = "opacity:0.5;"),
opts_selection(type = "single", only_shiny = FALSE)
)
)
htmltools::save_html(interactive_plot, "../HtmlWidget/ggiraph-6.html")
👋 After crafting hundreds of R charts over 12 years, I've distilled my top 10 tips and tricks. Receive them via email! One insight per day for the next 10 days! 🔥