library(tidyverse)
sexual_general_health <- read_csv("sexual_general_health.csv")Interactive beeswarm charts in R
You might be wondering what exactly is a beeswarm chart? A beeswarm chart is a type of chart used to show the distribution of a continuous variable. Unlike density plots or histograms, beeswarm charts show every individual data point. The points are placed so that they don’t overlap, make it look a bit like a swarm of bees - the name becomes clearer once you’ve seen an example!
Data
Of course, if we’re making a chart, we need some data to plot! We’re going to use the General health by sexual orientation data that we used to demonstrate beeswarm charts in our recent rainbowR workshop on Visualising the UK’s LGBTQ+ population.
For LGBTQ+ History Month, between 25 February and 13 March 2026, Kirstie Ken English and I are challenging people to produce visualisations using UK census data on LGBTQ+ people.
Share your visualisations and code via social media using #VisLGBTQ or send it to us via GitHub. Find more information and data on our rainbowR workshop GitHub repository.
Let’s load the data:
which looks like this:
head(sexual_general_health)# A tibble: 6 × 7
area_code area_name sexual_orientation general_health n population
<chr> <chr> <chr> <chr> <dbl> <dbl>
1 E06000001 Hartlepool Does not apply Does not apply 0 92300
2 E06000001 Hartlepool Does not apply Very good health 0 92300
3 E06000001 Hartlepool Does not apply Good health 0 92300
4 E06000001 Hartlepool Does not apply Fair health 0 92300
5 E06000001 Hartlepool Does not apply Bad health 0 92300
6 E06000001 Hartlepool Does not apply Very bad health 0 92300
# ℹ 1 more variable: percentage <dbl>
Let’s do a little bit of data wrangling to get it into the format we need for plotting. We’re going to make a chart showing the percentage of people in each area who reported bad or very bad health, split by sexual orientation.
The percentage column currently shows the percentage of the population in each area who responded with a specific combination of sexual_orientation and general_health. That’s not quite the percentage we want. Instead we need to calculate the population in each area who reported a specific sexual_orientation, and then calculate the percentage of those who responded with either "Bad health" or "Very bad health".
plot_data <- sexual_general_health |>
group_by(area_name, sexual_orientation) |>
mutate(population = sum(n)) |>
ungroup() |>
filter(
general_health %in% c("Bad health", "Very bad health"),
sexual_orientation != "Does not apply"
) |>
mutate(percentage = 100 * n / population) |>
group_by(area_name, sexual_orientation) |>
summarise(percentage = sum(percentage)) |>
ungroup()By default in R, categories are sorted alphabetically unless you specify otherwise. Although there are times when an alphabetical ordering of categories on a chart is appropriate, it’s not really going to make sense here. Instead, we’ll sort the sexual orientation categories from highest to lowest median percentage. Let’s calculate the median for each group:
summary_data <- plot_data |>
group_by(sexual_orientation) |>
summarise(med_perc = median(percentage)) |>
arrange(med_perc)And then convert the sexual_orientation category to a factor, using the order of the medians to set the order of the categories. And yes, I am mixing base R and tidyverse here - use mutate() if you’d prefer!
plot_data$sexual_orientation <- factor(
plot_data$sexual_orientation,
levels = summary_data$sexual_orientation
)Now we have the data that we need to make our beeswarm chart!
How to make a beeswarm chart in R
Before we get into interactivity, we’re going to start with figuring out how to make a static beeswarm chart. Luckily for us, the ggbeeswarm package will be doing pretty much all of the hard work! So let’s start by loading the package.
library(ggbeeswarm)There are multiple functions in the ggbeeswarm package that can be used to create beeswarm charts, with the different functions giving different layouts of how the points are jittered. Though geom_beeswarm() might seem like the obvious function, I tend to use geom_quasirandom() because I personally prefer the layout of the points it results in.
The geom_quasirandom() function works much like any other geom_ function in ggplot2. We start by passing in our data, and the aesthetic mapping to the ggplot() function. Then simply add geom_quasirandom().
ggplot(
data = plot_data,
mapping = aes(x = percentage, y = sexual_orientation)
) +
geom_quasirandom()Now we have a beeswarm chart! You can clearly compare the location and spread of the different distributions, but at the same time still see each individual data point. However, you can also clearly see there are some outliers and the first thing you want to know is ‘which areas are those?’ We could add some annotations to our static chart, but that can quickly get messy. Instead, beeswarm charts are often made interactive with a tooltip showing information about a point when it is hovered over.
Making an interactive beeswarm
There are several packages in R for making interactive charts. If you’ve already made a static chart with ggplot2 then by far the easiest option is to use the ggiraph package. It’s designed to make ggplot2 charts interactive, with drop-in replacements for the common geom_ functions.
library(ggiraph)Unfortunately, ggiraph doesn’t necessarily work out of the box with geoms provided by extensions. There is no geom_quasirandom_interactive() function. However, our beeswarm chart is essentially just geom_point() with a very specific layout. This means we can actually use geom_point() to make our chart, and just borrow the layout from the ggbeeswarm package.
ggplot(
data = plot_data,
mapping = aes(x = percentage, y = sexual_orientation)
) +
geom_point(
position = position_quasirandom(),
)This is identical to the plot we made earlier with geom_quasirandom(). Now, it’s easy for us to use the ggiraph package to make our chart interactive. We simple switch geom_point() for geom_point_interactive(). We also add in two further aesthetic mappings: tooltip and data_id. As you might expect, tooltip defines what text appears in the tooltip on hover. Here, we’ve simply added the area_name but you can use glue() or paste() to create custom tooltips containing static text, and values if you prefer. The data_id makes each graphical element uniquely identifiable, and ensures elements that share the same data_id are treated as the same thing. This means that when we hover over an area in one of the beeswarms, the same area in the other beeswarms will also be highlighted, enabling better comparison.
ggplot(
data = plot_data,
mapping = aes(x = percentage, y = sexual_orientation)
) +
geom_point_interactive(
mapping = aes(tooltip = area_name, data_id = area_name),
position = position_quasirandom(),
)You’ll notice that this chart still looks identical to our previous charts, and it’s not actually interactive yet. We’ll deal with that in just a moment. First, lets adjust the styling and add a little bit of text. I’m not going to go into too much detail here about styling because that’s not the main point of this blog post, but essentially we: make the points a nicer colour add some transparency; set the x axis range; add a subtitle and caption with information about the data; and adjust the theme to be a little bit cleaner looking.
g_int <- ggplot(
data = plot_data,
mapping = aes(x = percentage, y = sexual_orientation)
) +
geom_point_interactive(
mapping = aes(tooltip = area_name, data_id = area_name),
position = position_quasirandom(),
alpha = 0.7,
colour = alpha("#018E42", 0.7),
fill = "#018E42",
pch = 21
) +
scale_x_continuous(limits = c(0, 25)) +
labs(
x = "Percentage", y = NULL,
subtitle = "Percentage of population in each area reporting bad or very bad health by sexual orientation",
caption = "Source: General health by sexual orientation from Office for National Statistics | Graphic: Nicola Rennie"
) +
theme_minimal() +
theme(
plot.title.position = "plot",
plot.caption.position = "plot",
plot.title = element_text(face = "bold", margin = margin(b = 5)),
plot.subtitle = element_text(margin = margin(b = 5)),
plot.caption = element_text(margin = margin(t = 5)),
axis.title.x = element_text(hjust = 1, size = rel(0.9)),
panel.grid.minor.y = element_blank(),
plot.margin = margin(10, 15, 10, 10)
)
g_intNow we can deal with actually making it interactive. Simply printing charts made with geom_*_interactive() doesn’t result in an interactive chart. Instead, we need to save our chart as an object, and pass it into the girafe() function:
girafe(ggobj = g_int)Now when you hover over the points, you should see a tooltip showing the name of the area each point relates to. I don’t love the default styling of the tooltip, so we can edit the appearance of it using opts_tooltip() and passing this into the girafe() function. It’s also currently quite tricky to see which point you are currently hovering over, so we’ll add some more styling that makes the points larger when hovered over; and makes the non-hovered points more transparent.
girafe(
ggobj = g_int,
options = list(
opts_tooltip(
css = "
padding: 5pt;
font-size: 1rem;
background-color: #FFFFFF;
border-radius: 3px;
line-height: 1;
color: #000000;"
),
opts_hover(css = "fill:#F7D002;stroke:#000000;r:6px;opacity:1;"),
opts_hover_inv(css = "opacity:0.3;")
)
)And now we have a functional, aesthetically pleasing interactive beeswarm chart!
Remember that adding interactivity into your chart can impact the accessibility of it. You should add a text description of the chart that can be accessed by screen readers, and provide an alternative way to access the data i.e. through an accessible data download.
Ideally, you should also do some post-processing of your ggiraph chart to make sure that the <svg> contains an aria-label describing the data, as well as tabindex and role. How you do that will depend on how you are publishing your chart. You might find htmlwidgets::onRender() useful for injecting these elements when the chart is rendered.
Resources
Ready to make your own interactive beeswarm charts? Here are a few resources you might find helpful:
- The
ggbeeswarmGitHub repository contains several different examples, including ways to customise the layout. - The
ggiraphbook has lots of examples of making interactive charts, including dealing with fonts and using them in Shiny apps. - I did a talk at EdinbR last year, where I show how to create interactive charts using either
ggiraphor Observable. Check out the slides for some step-by-step examples.
Want to see an interactive beeswarm chart in action? Have a look at my article on statistical performance indicators around the world.
Reuse
Citation
@online{rennie2026,
author = {Rennie, Nicola},
title = {Interactive Beeswarm Charts in {R}},
date = {2026-02-27},
url = {https://nrennie.rbind.io/blog/interactive-beeswarm-r/},
langid = {en}
}



