Linking views with shiny
Accessing events in shiny
The plotly.js library emits custom events when a user interacts directly with a graph. The
event_data() function provides a mechanism for accessing the data corresponding to those events within a shiny app. The shiny app in Figure 3.13 is designed to demonstrate the most useful plotly events one may access via
event_data(): mouse hover (
"plotly_hover"), click (
"plotly_click"), and click+drag (
"plotly_selected"). All of these events return selections on the data scale, not on a pixel scale, which is useful for updating views.
There are currently four different modes for click+drag interactions in plotly.js, but only two will trigger a
"plotly_selected" event: rectangular and lasso selection. The other two drag modes, zoom and pan, both emit a
"plotly_relayout" event which could be useful for say, providing global context in relation to a zoom event and/or recomputing a model based on new x/y limits. In Figure 3.13, the default click+drag mode was set to rectangular selection set via the dragmode attribute, but the mode can also be changed interactively via the mode bar at the top of the graph.
The video in Figure 3.13 helps demonstrate how different user events cause different blocks of code to be evaluated on the R server. Conceptually, you can think of events as different inputs that becomes invalidated when the event is triggered by plotly.js. Moreover, similar to restrictions placed on references to input value(s) in shiny,
event_data() has to be called within a reactive expressions. As RStudio’s lesson on reactive expressions points out: “A reactive expression is an R expression that uses widget input [(e.g.,
event_data())] and returns a value.”
Any of the
render*() functions in shiny turn a regular R expression into a reactive expression. In Figure 3.13, every use of
event_data() appears within
renderPrint() since we only need to display the result of the event on the user interface with
verbatimTextOutput(). In the next section, we use the return result of
event_data() to display more interesting and informative views of user events.
Figure 3.13: A video demonstration of plotly events in shiny. The video can be accessed here
Obtaining data from a plotly event is easy, but updating view(s) based on the result of an event can be difficult. To start with something fairly easy, consider two scatterplots showing the same observations, but on different axes (i.e.., a subset of a scatterplot matrix). Figure 3.14 shows a linked lasso brush between two scatterplots. The main idea is that we first plot all the observations in black, then highlight the selection by adding an additional layer of selected points in red using the data returned by
event_data(). In order to guarantee that we can uniquely identify observations in the event data, it is also crucial that we attach a
key attribute to each observation (here the rownames of the data), which we can then use to filter the original data down to the selected observations.
Figure 3.14 consciously updates the source of the selection (the top plot) to match the visual characteristics of the target (the bottom plot). In general, whenever linking views to display graphical selection(s), matching the visual characteristics of the selection both the source and target(s) can aide interpretation, especially when using interactive graphics to present results to others. Although the update rule in Figure 3.14 is to simply layer on additional points, a full redraw is performed during the update, which can impact performance when dealing with a large amount of graphical elements.
Figure 3.14 could be made slightly more efficient by just changing the color of selected points, or dimming the non-selected points, rather than plotting an extra layer of points. However, this technique does not work for chart types that display aggregate values (e.g., how do you dim non-selected values in a box plot?). For this reason, in Linking views without shiny, selections are implemented as an additional layer, but avoid the full redraw required when updating plot via shiny reactive framework.
Figure 3.14: A video demonstration of linked brushing in a shiny app. The video can be accessed here and the code to run the example is here
Since the update rule is the same for each view in Figure 3.14, we end up with a lot of redundant code that can be made more modular, as shown here. Making code more modular not only makes for less reading, but it leaves you less prone to making mistakes. Since the only difference between the two plots is the x/y variables, we can write a function that accepts x/y variables as input, and output a plotly object. Since this function outputs a plotly object, and is dependent upon
event_data(), which can only be called within a reactive expression, this function can only be called within the
renderPlotly() function in the plotly package.
The linked brushing example in Figure 3.14 has bi-directional communication – a
"plotly_selected" event deriving from either view impacts the other view. In other words, each view can be either the source or target of the selection. Often times, we want one view to be the source of a selection, and related view(s) to be the target. Figure 3.15 shows a heatmap of a correlation matrix (the source of a selection) linked to a scatterplot (the target of a selection). By clicking on a cell in the correlation matrix, a scatterplot of the two variables is displayed below the matrix.
To update the scatterplot view, Figure 3.15 accesses
"plotly_click" events via the
event_data() function, but it also careful to not access click events triggered from the scatterplot. By strategically matching the value of the
source argument in the
event_data() functions, Figure 3.15 effectively restricts the scope of events to a specific plot (the heatmap).
Figure 3.15: A video demonstration of clicking on a cell in a correlation matrix to view the corresponding scatterplot. The video can be accessed here and the code to run the example is here
Another aspect of Figure 3.15 that makes it an interesting example is that the
key attribute is a matrix, matching the same dimensions of
z (i.e, the values displayed in each cell). For good reason, most linked views paradigms (including the paradigm discussed in Linking views without shiny) restrict linkage definitions to relational database schema. In this case, it is more efficient to implement the relation with a key matrix, rather than a column.