---
title: "Interactive graphics with {ggformula}"
subtitle: "Getting Started"
date: last-modified
author: Randall Pruim
engine: knitr
toc: true
vignette: >
  %\VignetteIndexEntry{Interactive graphics with {ggformula}}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
execute:
  engine: knitr
  eval: true
  echo: true
knitr:
  opts_chunk:
    collapse: true
    comment: '#>'
fig-width: 6
fig-height: 4
number-sections: true
number-depth: 3
embed-resources: true
---

# Interactive geoms, scales, and facets

```{r}
#| label: setup
#| include: false
set.seed(1234)
library(ggformula)
library(dplyr)
library(patchwork)
```

## Interactive geoms

The {ggiraph} package provides a number of interactive geoms.  {ggformula}
makes these available via `gf_*_interactive()` functions.
Interactive geoms provide several types of interaction.

* hover actions
    * easy tooltips using `tooltip = `
    * restyling elements upon hover
* selection of multiple elements at once 
* linking items across multiple plots 
* custom javascript to be applied when clicking on a graphical element with `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](https://www.ardata.fr/ggiraph-book/shiny.html).


## First example: Scatter plot with tooltips

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.

```{r}
#| label: load-ggformula
library(ggformula)
theme_set(theme_bw())
```



```{r}
#| label: cars-scatter
data(mtcars)
mtcars2 <- mtcars |>
  tibble::rownames_to_column(var = "carname")

cars_scatter <-
  mtcars2 |>
  gf_point_interactive(
    wt ~ drat,
    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 components 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.

```{r}
#| label: cars-scatter-tooltip
cars_scatter_tooltip <-
  mtcars2 |>
  gf_point_interactive(
    qsec ~ disp,
    tooltip = ~ glue::glue("{carname} ({cyl} cylinders)"),
    data_id = ~cyl,
    size = 3,
    hover_nearest = TRUE
  )

gf_girafe(cars_scatter_tooltip)

```


## Dealing with stats in interactive bar graphs

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.

```{r}
#| label: diamonds-bargraph
data(diamonds)
diamonds_bargraph <-
  diamonds |>
  gf_bar_interactive(
    ~color,
    fill = ~cut,
    tooltip = ~ after_stat(count),
    data_id = ~ as.character(cut)
  )

diamonds_bargraph |> gf_girafe()
```

:::{.callout-note}
### `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 by `gf_bar()` and `gf_bar_interactive()` computes 
these counts (and some additional things),
storing the counts in a column called `count`, which is only available
after the statistic has been computed.
`tooltip = ~ after_stat(count)` creates the tooltip variable after
`count` 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()`.^[Technically, we are seeing the data after both
the statistic and the scale have been applied. See below.]

```{r}
#| label: layer_data

diamonds_bargraph |> layer_data() |> head(3)
```

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` and `data_id` also appear here.
* We no longer have access to columns like `color` (it has been mapped to `x` with values 1, 2, 3, ...) or `cut` (instead we have `fill` which shows
that actual color being used to fill the bars).  These values are 
computed by scales in a third stage of data transformation.
:::

:::{.callout-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`, which `after_stat(count)` does not know about. If this is not desirable, we will need to refine either
`tooltip` or `data_id` to make them match.  See @sec-finer-control.
:::

### Finer control {#sec-finer-control}

There are several ways to get finer control over what is highlighted and 
what information appears in the tooltip when we hover.

#### Method 1: Summarising before plotting

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.

```{r}
#| label: diamonds-colgraph-1
library(dplyr)
diamonds |>
  group_by(color, cut) |>
  summarise(count = n()) |>
  gf_col_interactive(
    count ~ color,
    fill = ~cut,
    tooltip = ~ glue::glue("color: {color}, cut: {cut}, count: {count}"),
    data_id = ~ glue::glue("{cut} - {color}")
  ) |>
  gf_girafe()
```

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.


```{r}
#| label: diamonds-col-graph-2
diamonds |>
  group_by(color, cut) |>
  summarise(count = n()) |>
  gf_col_interactive(
    count ~ color,
    fill = ~cut,
    tooltip = ~ glue::glue("color: {color}, cut: {cut}, count: {count}")
  ) |>
  gf_girafe()
```

#### Method 2: Using `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 
highlighting and tooltip text.

```{r}
#| label: diamonds-bargraph2
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
  )
diamonds_bargraph_2 |>
  gf_girafe()
```

:::{.callout-note}
### `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.

1. **start**: 
The process begins with the original data that we provide.
By default, this is where mapping happens.

2. **after_stat**: The starting data are transformed by a statistic (or stat) that computes any summaries of the data. 

3. **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 named `tooltip` that contains the text identifying the
color and cut of a diamond.

* `after_stat = glue::glue("cut: {tooltip}; count = {count}"))` uses
`tooltip` value created at the start stage to create a new `tooltip` column 
after the stat has been calculated and `count` is available.

If you have not encountered `stage()` before, you can learn more
in the [documentation for `stage()`](https://ggplot2.tidyverse.org/reference/aes_eval.html).

:::

## Interactive scales

Interactive scales can be used inside `gf_refine()` and generate interactive guides (legends, axes, etc.).

```{r}
#| label: interactive-scales
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

Interactive faceting requires three things:

1. The use of `gf_facet_wrap_interacive()` or `gf_facet_grid_interactive()`, in
place of `gf_facet_wrap()` or `gf_facet_grid()`;
2. The use of an interactive labeller (`labeller = gf_labeller_interactive()`) to create the labels; and 
3. A theme that enables facet text and/or strips to be interactive.


```{r}
#| label: faceting
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
    )
  ) 

  diamonds_bargraph_4 |> gf_girafe()
```

:::{.callout-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 highlighted.
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` and `group` columns in the layer data.

```{r}
#| label: diamonds-bargraph4
diamonds_bargraph_4 |> layer_data() |> slice_sample(n=4)
```

:::

## Interactive themes

{ggiraph} provides 3 interactive 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.


```{r}
#| label: theme-facets-interactive
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 @sec-customizing to see how to improve the hover interaction.


## Interacting with multiple plots using {patchwork}

If we use {patchwork} to arrange multiple plots into a grid, selecting points
in one plot will highlight them in both.

```{r}
#| label: patchwork
library(patchwork)


cars_scatter_2 <-
  mtcars2 |>
  gf_point_interactive(
    disp ~ qsec,
    color = ~mpg,
    tooltip = ~carname,
    data_id = ~carname,
    hover_nearest = TRUE,
    size = 3
  )

gf_girafe(cars_scatter / cars_scatter_2)
```

## Click actions with JavaScript

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.

### Alerts

```{r}
#| label: js-alert
  mtcars2 |>
  gf_point_interactive(
    wt ~ drat,
    color = ~mpg,
    data_id = ~carname, 
    onclick = ~glue::glue('alert("Here is some info for {carname} ...")'),
    size = 3
  ) |>
    gf_girafe()
```

### Opening another webpage

In the example below, we use this to open a webpage with related information.

```{r}
#| label: js-window-open
  mtcars2 |>
  gf_point_interactive(
    wt ~ drat,
    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()
```
# Customizing girafe animations {#sec-customizing}


We can customize the interactive features of our plots in one of two ways:

1. Setting `options = list( ... )` in the call to `gf_girafe()`, or 
2. Using `set_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.


## CSS styling

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 delimiters 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 text
* `fill`: 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 content
* `opacity`: opacity (a number between 0 and 1)

:::{.callout-warning}
### 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.
:::

:::{.callout-warning}
### 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.
:::

## Hover options

### Hover CSS

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 color
*  `stroke`: color
*  `stroke-width`: border width
*  `r`: circle radius
*  `opacity`: opacity (a number between 0 and 1)

We can use opacity to improve our interactive facets.

```{r}
#| label: hover-css
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`.

```{r}
#| label: girafe-css
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()`.

```{r}
#| label: label-interactive
mtcars2[1:6, ] |>
  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;"
        )
      )
    )
  )
```

### Key and inverse hovering

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()`.


:::{.callout-tip}
### 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.
:::

```{r}
#| label: weather-hover-inv
mosaicData::Weather |>
  gf_line_interactive(
    high_temp ~ date,
    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)
    )
  )

```


## Tooltip options

### Position

`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.

```{r}
#| label: tooltip-position
cars_scatter |>
  gf_girafe(
    options = list(
      opts_tooltip(offx = 0, offy = -30, use_cursor_pos = FALSE)
    )
  )
```


### Autocoloring

If we set `use_fill = TRUE`, then the fill color of the tooltip will match the 
color of the plot element it is associated to.

```{r}
#| label: autocoloring
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;"
      )
    )
  )
```

:::{.callout-warning}
### 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.
:::


## Zoom options

We can enable panning and zooming by choosing a value of `max` greater than 1 in
`opts_zoom()`.

```{r}
#| label: zoom
cars_scatter |>
  gf_girafe(
    options = list(opts_zoom(max = 5))
  )
```

## Global options

We can set options globally using `set_girafe_defaults()`

```{r}
#| label: 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)
    )
  )
```

:::{.callout-warning}
Notice that using `opts_tooltip()` in the `options` argument of `gf_girafe()` not only changes `offx`, `offy`, and `use_cursor_pos`, but also causes `css` to 
revert to the package defaults rather than to the session defaults we set. 
:::

