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:
shiny::runApp(system.file("examples", "shiny-brush", package = "glyph"))
knitr::include_graphics("shiny-app.png")
shiny is not installed with glyph. Install it with
install.packages("shiny") before you try this.
This is the whole app:
The rest of this section goes through it.
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.
renderGlyph() takes the same pipeline you would print at the console:
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.
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.
The value changes when the user does one of these:
integer(0).interact(click = "select"). The mark's
rows are selected. Clicking it again clears the selection.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.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.
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.
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.
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:
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.
Some things are not done yet:
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.