---
title: "Using glyph in Shiny"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Using glyph in Shiny}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

glyph plots work in Shiny apps. `glyphOutput()` puts a plot in the page and
`renderGlyph()` draws it from the server. When the user brushes points,
clicks a mark or clicks a legend entry, the server gets the row numbers of
what they selected.

This article is built around a small app that comes with glyph. You brush
points in a scatterplot of `mtcars`, and a table lists the cars you
selected. Run it with:

```{r, eval = FALSE}
shiny::runApp(system.file("examples", "shiny-brush", package = "glyph"))
```

```{r, echo = FALSE, out.width = "100%", fig.alt = "The example app: a scatterplot of car weight against miles per gallon, with a grey brush rectangle over nine light, efficient cars. Below it, the text '9 cars selected.' and a table of those nine cars."}
knitr::include_graphics("shiny-app.png")
```

shiny is not installed with glyph. Install it with
`install.packages("shiny")` before you try this.

## The app

This is the whole app:

```{r, eval = FALSE, code = readLines(system.file("examples", "shiny-brush", "app.R", package = "glyph"))}
```

The rest of this section goes through it.

### Placing the plot

`glyphOutput("plot", height = "400px")` reserves space for the plot in the
page. The width is 100% by default. The plot is drawn to fit the space it
is given.

### Drawing the plot

`renderGlyph()` takes the same pipeline you would print at the console:

```{r, eval = FALSE}
output$plot <- renderGlyph(
  glyph(cars, x = wt, y = mpg) |>
    mark_point(color = factor(cyl)) |>
    interact(brush = TRUE, tooltip = TRUE)
)
```

It also takes a layout from `compose()`, or a widget from `render()`. If
the expression returns `NULL`, the output is left empty.

The plot needs something the user can select with. Here that is
`interact(brush = TRUE)`. Without it, `input$plot_selected` never changes.

### Reading the selection

The selection arrives as `input$plot_selected`: the output's name followed
by `_selected`. It holds the row numbers of the data frame given to
`glyph()`, so `cars[input$plot_selected, ]` gives the selected rows. When
nothing is selected it is `NULL`. The table uses `req()` to show nothing
until there is a selection.

In a Shiny module, use `glyphOutput(ns("plot"))` in the module's UI, and
the selection arrives in the module server as `input$plot_selected`.

## When the server hears about it

The value changes when the user does one of these:

- Ends a brush. The value is sent when the mouse button is released, not
  while the brush is dragged. A brush that takes in no points sends
  `integer(0)`.
- Clicks a mark in a plot with `interact(click = "select")`. The mark's
  rows are selected. Clicking it again clears the selection.
- Clicks an entry in a legend added with `selection(type = "legend")`.
  Clicking an entry hides its rows, and the rows still shown are the
  selection. With every entry shown again, the value is `NULL`.
- Clears a brush, with the "Clear selection" button, the Escape key, or a
  click on an empty part of the plot. The value becomes `NULL`.

Each new selection replaces the last one.

A redraw also clears the selection, and sends `NULL`. The plot is redrawn
when `renderGlyph()` runs again, for example because a reactive value it
uses has changed. It is also redrawn when its output changes size, for
example when the browser window is resized.

So keep `input$plot_selected` out of the plot's own `renderGlyph()`. A plot
that depends on its own selection is redrawn each time the user selects
something, and the selection is lost straight away. Use the selection in
other outputs, as the example does with its table.

## What the row numbers count

The numbers are positions in the data frame, like those from `which()`.
They are not row names.

They count every row of the data frame, including rows that aren't drawn.
A mark leaves out rows with a missing x or y value, and warns that it did.
Those rows are never selected, but the rows after them keep their numbers.
A computed mapping such as `x = log(wt)` doesn't change the numbers either.

```{r, eval = FALSE}
df <- data.frame(x = c(1, NA, 3, 4), y = c(2, 5, 1, 3))
glyph(df, x = x, y = y) |> mark_point() |> interact(brush = TRUE)
# Brushing every point gives c(1L, 3L, 4L).
```

Selecting a box, violin, histogram bin or polygon selects every row it
summarises. A sina mark draws a point for each row, so each point is one
row.

A mark that has its own `data`, such as
`mark_line(data = trend, x = x, y = fit)`, draws rows of another data
frame. It adds nothing to the value.

## Facets and layouts

A `facet()` grid splits one data frame into panels, so its value is one
vector. Each panel has its own brush, and the value holds the rows
selected in any of them.

A `compose()` layout can show different data in each panel. Its value is a
list with one entry per panel, in the order the panels were given. If
`compose()` was called with names, the entries have those names:

```{r, eval = FALSE}
output$plots <- renderGlyph(
  compose(
    cars = glyph(mtcars, x = wt, y = mpg) |>
      mark_point() |>
      interact(brush = TRUE),
    flowers = glyph(iris, x = Sepal.Length, y = Petal.Length) |>
      mark_point() |>
      interact(brush = TRUE)
  )
)
```

Here `input$plots_selected` is `NULL` until something is selected. After
the user brushes a few cars it is, for example,
`list(cars = c(3L, 18L, 20L), flowers = NULL)`. Each entry holds row
numbers of that panel's data, so `mtcars[input$plots_selected$cars, ]`
gives the cars. A panel with no selection is `NULL`. When every panel is
back to `NULL`, so is the whole value. A layout nested inside another
gives a nested list of the same shape.

With `linked_selections = TRUE`, a selection in one panel highlights the
matching rows in the others. Only the selection itself is reported. A
panel that only highlights another panel's selection reports `NULL`.

## Limits

Some things are not done yet:

- Selections only go from the browser to the server. The server can't set
  or clear a selection.
- Only selections are sent. Zooming, hovering and tooltips stay in the
  browser.
- A redraw clears the selection, as described above, including a redraw
  caused by resizing.
- In a plot without a brush there is no "Clear selection" button, and
  Escape does nothing. To clear a click selection, click the selected mark
  again.
- A legend matches a row to its entry by looking through the row's values
  for one that is also a colour level. If another column holds a value
  that is also a colour level, the row can be matched to the wrong entry.
