library(ggformula)
theme_set(theme_bw())
Getting Started
2025-08-27
The {ggiraph} package provides a number of interactive geoms. {ggformula} makes these available via gf_*_interactive()
functions. Interactive geoms provide several types of interaction.
tooltip =
onclick =
.Each type of interaction can be customized for both behavior and style.
Although not discussed here, {ggiraph} transforms graphics into reactive objects, making various events and selections available for shiny apps.
Interactive geoms provide two new aesthetics that can be used used to help identify or provide additional information about individual points or sets of points.
tooltip
provides the text (or HTML) to be displayed in the tooltip.data_id
provides and identifier for a selection.library(ggformula)
theme_set(theme_bw())
data(mtcars)
<- mtcars |>
mtcars2 ::rownames_to_column(var = "carname")
tibble
<-
cars_scatter |>
mtcars2 gf_point_interactive(
~ drat,
wt color = ~mpg,
tooltip = ~carname, # show carname when hovering on a point
data_id = ~carname, # unique identifier -- selection is a single point
hover_nearest = TRUE,
size = 3
)
# to display the graph with interactive compontents enabled, use
# gf_girafe() to convert it to an HTML widget.
gf_girafe(cars_scatter)
In the previous example, data_id
was a unique identifier for the points. In the next example, data_id
identifies groups of cars (those with the same number of cylinders). The hover text identifies both the car name and the number of cylinders.
<-
cars_scatter_tooltip |>
mtcars2 gf_point_interactive(
~ disp,
qsec tooltip = ~ glue::glue("{carname} ({cyl} cylinders)"),
data_id = ~cyl,
size = 3,
hover_nearest = TRUE
)
gf_girafe(cars_scatter_tooltip)
Many graphics – bar graphs, histograms, boxplots, density plots, etc. – are built not from our original data but from some transformation of the data. Understanding the role of data transformations computed by statistics and scales can be important to creating graphics.
Here is an example where tooltips allow us to see how many items are represented in each segment of a stacked bar plot.
data(diamonds)
<-
diamonds_bargraph |>
diamonds gf_bar_interactive(
~color,
fill = ~cut,
tooltip = ~ after_stat(count),
data_id = ~ as.character(cut)
)
|> gf_girafe() diamonds_bargraph
after_stat()
By default, when we map variables, the mapping uses the original data we provide for the layer. When a layer is created from summarised data (bars defined by counts, boxplots defined by 5-number summaries, densities for density plots, etc.), we have the option to do mapping after the summarising statistic as been applied. That’s what
after_stat()
is doing here.We don’t have a column named
count
in our data, but the stat used bygf_bar()
andgf_bar_interactive()
computes these counts (and some additional things), storing the counts in a column calledcount
, which is only available after the statistic has been computed.tooltip = ~ after_stat(count)
creates the tooltip variable aftercount
has been calculated.In examples like this, it can be handy to know what data are available after the summarising statistic has been applied. We can inspect this with
layer_data()
.1|> layer_data() |> head(3) diamonds_bargraph #> count prop x flipped_aes fill data_id PANEL group tooltip y ymin ymax #> 1 163 1 1 FALSE #440154FF Fair 1 1 163 6775 6612 6775 #> 2 224 1 2 FALSE #440154FF Fair 1 2 224 9797 9573 9797 #> 3 312 1 3 FALSE #440154FF Fair 1 3 312 9542 9230 9542 #> xmin xmax colour linewidth linetype alpha width #> 1 0.55 1.45 NA 0.5 1 NA 0.9 #> 2 1.55 2.45 NA 0.5 1 NA 0.9 #> 3 2.55 3.45 NA 0.5 1 NA 0.9
Some things to notice:
- There is one row for each of the bars.
- We have access to some new columns that provide information about each of our bars:
count
,ymin
,ymax
,xmin
,xmax
. These have been computed by the stat.- We can see that our
tooltip
anddata_id
also appear here.- We no longer have access to columns like
color
(it has been mapped tox
with values 1, 2, 3, …) orcut
(instead we havefill
which shows that actual color being used to fill the bars). These values are computed by scales in a third stage of data transformation.
Warning
Note that in the previous graphic, the count displayed when hovering is the count for a single segment, not for all of the segments that change color. The latter is determined by
data_id
, whichafter_stat(count)
does not know about. If this is not desireable, we will need to refine eithertooltip
ordata_id
to make them match. See Section 1.3.1.
There are several ways to get finer control over what is highlited and what information appears in the tooltip when we hover.
Sometimes it is better to summarise the data ourselves before creating a graphic. gf_col_interactive()
is similar to gf_bar_interacive()
, but is designed to work with data that have already been summarised.
library(dplyr)
|>
diamonds group_by(color, cut) |>
summarise(count = n()) |>
gf_col_interactive(
~ color,
count fill = ~cut,
tooltip = ~ glue::glue("color: {color}, cut: {cut}, count: {count}"),
data_id = ~ glue::glue("{cut} - {color}")
|>
) gf_girafe()
#> `summarise()` has grouped output by 'color'. You can override using the
#> `.groups` argument.
Our use of data_id
here limits the hover highlighting to one bar segment (defined by cut and color). If we omit data_id
in the previous example, we still get the hover text, but the bar segment we are hovering on does not change color.
|>
diamonds group_by(color, cut) |>
summarise(count = n()) |>
gf_col_interactive(
~ color,
count fill = ~cut,
tooltip = ~ glue::glue("color: {color}, cut: {cut}, count: {count}")
|>
) gf_girafe()
#> `summarise()` has grouped output by 'color'. You can override using the
#> `.groups` argument.
stage()
Alternatively, we might prefer to let gf_bar_interactive()
take care of the data summarising for us, but still have finer control over the highliting and tooltip text.
<-
diamonds_bargraph_2 |>
diamonds gf_bar_interactive(
~color,
fill = ~cut,
tooltip = ~ stage(
start = glue::glue("color: {color}; cut: {cut}"),
after_stat = glue::glue("{tooltip}; count = {count}")
),data_id = ~ glue::glue("{cut} -- {color}"),
size = 3
)#> Warning in (function (mapping = NULL, data = NULL, stat = "count", position =
#> "stack", : Ignoring unknown parameters: `size`
|>
diamonds_bargraph_2 gf_girafe()
stage()
As mentioned above, when ggplot2 graphics are built, the data goes through a sequence of transformations. We can do mapping at three stages along the way.
start: The process begins with the original data that we provide. By default, this is where mapping happens.
after_stat: The starting data are transformed by a statistic (or stat) that computes any summaries of the data.
after_scale: The after_stat data is further transformed by scales that compute the specific positions, colors, etc. that are used.
The use of
stage()
allows us to map the same quantity to different values at different stages in this process. In our example,
start = glue::glue("color: {color}; cut: {cut}"),
creates a column namedtooltip
that contains the text identifying the color and cut of a diamond.
after_stat = glue::glue("cut: {tooltip}; count = {count}"))
usestooltip
value created at the start stage to create a newtooltip
column after the stat has been calculated andcount
is available.If you have not encountered
stage()
before, you can learn more in the documentation forstage()
.
Interactive scales can be used inside gf_refine()
and generate interactive guides (legends, axes, etc.).
<-
diamonds_bargraph_3 |>
diamonds_bargraph gf_refine(
scale_fill_viridis_d_interactive(
begin = 0.1,
end = 0.7,
option = "D",
data_id = function(breaks) as.character(breaks),
tooltip = function(breaks) glue::glue("break: {as.character(breaks)}")
)
)
|>
diamonds_bargraph_3 gf_girafe()
By themselves, interactive scales are not that interesting. But key selections can be turned into reactive values for use in things like shiny apps. See https://www.ardata.fr/ggiraph-book/shiny.html.
Interactive faceting requires three things:
gf_facet_wrap_interacive()
or gf_facet_grid_interactive()
, in place of gf_facet_wrap()
or gf_facet_grid()
;labeller = gf_labeller_interactive()
) to create the labels; and<-
diamonds_bargraph_4 |>
diamonds_bargraph_3 gf_theme(
strip.text = element_text_interactive(),
strip.background = element_rect_interactive()
|>
) gf_facet_wrap_interactive(
~clarity, # or vars(clarity)
interactive_on = "both",
ncol = 2,
labeller = gf_labeller_interactive(
tooltip = ~ paste("this is clarity", clarity),
data_id = ~clarity
)
)
|> gf_girafe() diamonds_bargraph_4
Warning
Now that we have added facets, we again have the situation where the counts displayed are for an individual bar segment, even though segments in other facets are also being highlited. This is because faceting further partitions the data before the stat is applied. This is required so that each facet knows the size of the bar segments to display.
Note the
PANEL
andgroup
columns in the layer data.|> layer_data() |> slice_sample(n=4) diamonds_bargraph_4 #> count prop x flipped_aes fill data_id PANEL group tooltip y ymin ymax #> 1 608 1 3 FALSE #43BF71FF Ideal 3 31 608 608 0 608 #> 2 32 1 6 FALSE #482576FF Fair 4 6 32 1169 1137 1169 #> 3 202 1 7 FALSE #1E9C89FF Premium 4 28 202 434 232 434 #> 4 209 1 7 FALSE #1E9C89FF Premium 3 28 209 452 243 452 #> xmin xmax colour linewidth linetype alpha width #> 1 2.55 3.45 NA 0.5 1 NA 0.9 #> 2 5.55 6.45 NA 0.5 1 NA 0.9 #> 3 6.55 7.45 NA 0.5 1 NA 0.9 #> 4 6.55 7.45 NA 0.5 1 NA 0.9
{ggiraph} provides 3 intereactive elements for use in interactive themes:
element_text_interactive()
element_rect_interactive()
element_line_interactive()
These are drop-in replacements for their non-interactive counterparts.
They can be used with gf_theme()
to add interactive theme elements to an individual plot, as was done the previous example;
They can be used with set_girafe_defaults()
; or
They can be included in custom theme functions.
{ggformula} provides theme_facets_interactive()
for adding interactive elements for faceting to a theme of your choice.
|>
diamonds_bargraph_3 gf_theme(theme_facets_interactive(theme_minimal())) |>
gf_facet_wrap_interactive(
~clarity, # or vars(clarity)
interactive_on = "both",
ncol = 2,
labeller = gf_labeller_interactive(
tooltip = ~ paste("this is clarity", clarity),
data_id = ~clarity
)|>
) gf_girafe()
We’ll return to this example in Section 2 to see how to improve the hover interaction.
If we use {patchwork} to arrange multiple plots into a grid, selecting points in one plot will highlight them in both.
library(patchwork)
<-
cars_scatter_2 |>
mtcars2 gf_point_interactive(
~ qsec,
disp color = ~mpg,
tooltip = ~carname,
data_id = ~carname,
hover_nearest = TRUE,
size = 3
)
gf_girafe(cars_scatter / cars_scatter_2)
If you know some JavaScript, you can create click actions for interactive plot elements by passing the JavaScript that should be executed as onclick
. In this section we include just two example uses of JavaScript.
|>
mtcars2 gf_point_interactive(
~ drat,
wt color = ~mpg,
data_id = ~carname,
onclick = ~glue::glue('alert("Here is some info for {carname} ...")'),
size = 3
|>
) gf_girafe()
In the example below, we use this to open a webpage with related information.
|>
mtcars2 gf_point_interactive(
~ drat,
wt color = ~ mpg,
data_id = ~ carname,
tooltip = ~ carname,
onclick = ~ glue::glue('window.open("https://en.wikipedia.org/w/index.php?search={carname}")'),
size = 3
|>
) gf_girafe()
We can customize the interactive features of our plots in one of two ways:
options = list( ... )
in the call to gf_girafe()
, orset_girafe_defaults( ... )
.In either case we replace ...
with calls to one or more of the following:
fonts = list(...)
opts_sizing = opts_sizing(...)
opts_tooltip = opts_tooltip(...)
opts_hover = opts_hover(...)
opts_hover_key = opts_hover_key(...)
opts_hover_inv = opts_hover_inv(...)
opts_hover_theme = opts_hover_theme(...)
opts_selection = opts_selection(...)
opts_selection_inv = opts_selection_inv(...)
opts_selection_key = opts_selection_key(...)
opts_selection_theme = opts_selection_theme(...)
opts_zoom = opts_zoom(...)
opts_toolbar = opts_toolbar(...)
Girafe animations are produced in SVG (scalable vector graphics) format. We can customize how SVGs appear using CSS (cascading style sheets). So many of these functions are utilities to help us create the correct CSS. Some options require the user to provide some CSS as a string of semi-colon separated key-value pairs, where key-value pairs are separated by colons. But for many options we can avoid writing CSS directly using these helper functions.
The style of many interactive elements is determined by a character string containing CSS styling. Each CSS declaration includes a property name and an associated value. Property names and values are separated by colons and name-value pairs always end with a semicolon. Spaces can be added around delimeters to improve readability. For example color:gray; text-align:center;"
.
Common CSS properties include:
color
: color for points, etc.stroke
: color for lines, text, etc.background-color
: background color for textfill
: fill color for points, rectangles, etc.border-style
, border-width
, border-color
: border properties for rectangles, can be combined as in border: 5px solid red;
width
, height
: size (of tooltip, for example)padding
: space around contentopacity
: opacity (a number between 0 and 1)Color keys
Notice that the names of the keys for setting color vary among the various kinds of elements. To make things more confusing, text elements have both stroke and fill. Text will often look better if the stroke is removed, unless is is large enough to have substantial space within the stroke.
Don’t include curly braces
If you are familiar with CSS, you might be tempted to wrap your CSS string in curly braces.
gf_girafe()
takes care of adding those for you, so don’t include them in your string.
Use opts_hover
to style hovered data elements, opts_hover_inv
to style non-hovered data elements, and opts_hover_key
to style hovered guide elements. Common CSS properties for styling these elements include
fill
: background colorstroke
: colorstroke-width
: border widthr
: circle radiusopacity
: opacity (a number between 0 and 1)We can use opacity to improve our interactive facets.
|>
diamonds_bargraph_3 gf_theme(theme_facets_interactive(theme_minimal())) |>
gf_facet_wrap_interactive(
~clarity, # or vars(clarity)
interactive_on = "both",
ncol = 2,
labeller = gf_labeller_interactive(
tooltip = ~ paste("this is clarity", clarity),
data_id = ~clarity
)|>
) gf_girafe(
options = list(
opts_hover("fill:red; opacity: 0.5")
) )
This still leaves room for some improvement as our hover option is affecting both the strip rectangle and the strip text.
girafe_css()
Sometimes we need finer control over what gets styled by our css. For example, when using interactive facets or gf_label_interactive()
, the interactive elements include both text and rectangles, which we may wish to style differently. girafe_css()
provides this finer control. The css
argument provides a starting point which can be overridden with the subsequent arguments: text
, point
, line
, area
(used for rects, polygons, and paths), and image
.
|>
diamonds_bargraph_3 gf_theme(theme_facets_interactive(theme_minimal())) |>
gf_facet_wrap_interactive(
~clarity, # or vars(clarity)
interactive_on = "both",
ncol = 2,
labeller = gf_labeller_interactive(
tooltip = ~ paste("this is clarity", clarity),
data_id = ~clarity
)|>
) gf_girafe(
options = list(
opts_hover(
css = girafe_css(
css = "fill:red; opacity:0.7; stroke:black; stroke-width:3px;",
text = "stroke:none; fill:white; opacity:0.9;"
)
)
) )
Here is another example, this time using gf_label_interactive()
.
1:6, ] |>
mtcars2[gf_label_interactive(qsec ~ disp, label = ~carname, data_id = ~carname) |>
gf_girafe(
options = list(
opts_hover(
css = girafe_css(
css = "fill:yellow;",
text = "stroke:none; fill:red;"
)
)
) )
Styling hovering on a guide element (part of the legend or key) is handled with opts_hover_key()
in the same way we used opts_hover()
. We can also style the non-selected elements with opts_hover_inv()
.
Use low opacity in non-selected elements to make highlighted elements stand out.
The use of low opacity in non-hovered elements can be used to highlight the selected elements.
::Weather |>
mosaicDatagf_line_interactive(
~ date,
high_temp color = ~city,
show.legend = FALSE,
tooltip = ~city,
data_id = ~city
|>
) gf_facet_wrap_interactive(
~year,
ncol = 1,
scales = "free_x",
labeller = gf_labeller_interactive(
data_id = ~year,
tooltip = ~ glue::glue("This is the year {year}")
)|>
) gf_theme(theme_facets_interactive()) |>
gf_girafe(
options = list(
opts_hover_inv(css = "opacity:0.2;"),
opts_hover(css = "stroke-width:2;", nearest_distance = 40),
opts_tooltip(use_cursor_pos = FALSE, offx = 0, offy = -30)
) )
opts_tooltip()
has three arguments for determining the position of the tooltip:
use_cursor_pos
– a logical indicating whether the tooltip position is relative to the cursor position (default) or to the upper left corner of the plot.offx
, offy
– the number of pixels to offset the tooltip from this base position.|>
cars_scatter gf_girafe(
options = list(
opts_tooltip(offx = 0, offy = -30, use_cursor_pos = FALSE)
) )
If we set use_fill = TRUE
, then the fil color of the tooltip will match the color of the plot element it is associated to.
|>
diamonds_bargraph_3 gf_girafe(
options = list(
opts_tooltip(
use_fill = TRUE,
offx = 0,
offy = 0,
use_cursor_pos = FALSE,
css = "border: 2px solid black; color: aliceblue; border-radius: 4px; padding: 6px;"
)
) )
A downside of
use_fill = TRUE
Depending on your color scheme, you may find it hard to find a text color that works well over all the different fill colors.
We can enable panning and zooming by choosing a value of max
greater than 1 in opts_zoom()
.
|>
cars_scatter gf_girafe(
options = list(opts_zoom(max = 5))
)
We can set options globally using set_girafe_defaults()
set_girafe_defaults(
# set colors for
opts_hover = opts_hover(
css = "fill:yellow;stroke:black;stroke-width:3px;r:10px;"
),opts_hover_inv = opts_hover_inv(css = "opacity:0.5"),
# allow zooming/panning up to 4x size
opts_zoom = opts_zoom(min = 1, max = 4),
opts_tooltip = opts_tooltip(
css = "padding: 2px; border: 4px solid navy; background-color: steelblue; color: white; border-radius: 8px"
),opts_sizing = opts_sizing(rescale = TRUE),
opts_toolbar = opts_toolbar(
saveaspng = FALSE,
position = "bottom",
delay_mouseout = 5000
)
)
|>
cars_scatter gf_girafe()
|>
cars_scatter gf_girafe(
options = list(
opts_tooltip(offx = 0, offy = -25, use_cursor_pos = FALSE)
) )
Warning
Notice that using
opts_tooltip()
in theoptions
argument ofgf_girafe()
not only changesoffx
,offy
, anduse_cursor_pos
, but also causescss
to revert to the package defaults rather than to the session defaults we set.
Technically, we are seeing the data after both the statistic and the scale have been applied. See below.↩︎