Making data dashboard plots talk to each other with ggiraph and Shiny
Interactive dashboards are a great way to present data in an appealing and tangible way. Normally we use radio buttons, sliders, knobs, or dropdowns etc. as inputs to inferface with dashboards. With interactive plots, we can use one plot as an input for another and thereby produce a less cluttered, sleaker dashboard.
Programming data dashboards, e.g. with Shiny
for R
, involves in essence linking some input — a slider, menu, or button — with some output — a table, plot, or event. The typical user interface of a dashboard therefore looks something like this: Front and center is some plot or table that we want to present, and surrounding it is some array of knobs and buttons that are used to manipulate the data shown to the user.
Interactive plots featuring tooltips or data selection and highlighting functions can add further tangibility, but they can also be used as inputs for other plots or other types of outputs.
The ggiraph
package for R
, which is somewhat of a competitor to the perhaps better known plotly
library, provides just this functionality. The package’s author, David Gohel, provides an example (run_girafe_example("crimes")
), but it is IMHO a bit hidden in the documentation (https://davidgohel.github.io/ggiraph/articles/offcran/shiny.html) and also not so easy decipher at first (also IMO at least).
I provide a more trimmed down example and illustration, which should be easy to understand given some experience with R
, Shiny
, and ggplot2
.
To provide some background first:ggiraph
essentially adds an interactive layer to a ggplot
plot: If geom_point()
adds a scatterplot-layer to a ggplot()
object, then geom_point_interactive()
adds the same layer, but with interactivity such as tooltips that appear upon hovering over a datapoint and display additional info.
The workflow to produce a ggiraph
plot is similar to producing an interactive plot with ggplot2
and plotly
: One first creates the plot as a regular, non-interactive plot and then transforms it by passing it through the girafe()
function. Below is a simple example using the mpg
fuel-economy dataset, in which we relate cars’ engine displacement to their highway miles per gallon. Using ggiraph
, we also add a tooltip that displays cars’ model upon hovering over the graph.
library(ggplot2)
library(ggiraph)# Load data
data <- mpg# We produce a regular ggplot() object - but with the geom_point_interactive layer instead of the regular geom_point() layer:
scatter <- ggplot(data,aes(x=displ,y=hwy)) +
geom_point_interactive(aes(tooltip=model,data_id=model))# Then we transform the standard plot to produce an interactive one:
girafe(ggobj = scatter)
In the geom_point_interactive()
function, we specify that we want the model-variable printed in the tooltip. We also give the individual points shown their model as their data_id
.
ggiraph
integrates nicely with Shiny
. To produce an interactive plot in a Shiny
application, we follow the same workflow as shown above in our server
-function, but instead of using the renderPlot()
-function, we use the renderGirafe()
-function. On the UI
-side, we add the plot using the girafeOutput()
-function instead of the regular plotOutput()
-function. Below is again a simple example based on the code shown above:
library(shiny)
library(ggplot2)
library(ggiraph)# Load data
data <- mpgui <- fluidPage(
girafeOutput("plot1")
)server <- function(input, output, session) {output$plot1 <- renderGirafe({
scatter <- ggplot(data,aes(x=displ,y=hwy)) +
geom_point_interactive(aes(tooltip=model,data_id=model))
x <- girafe(ggobj = scatter)
x
})
Now comes the interesting part: When clicking on individual data points in a ggiraph
plot in a Shiny
app, ggiraph
produces reactive values that contain information about the selected data points. We can use these as inputs for other outputs in Shiny
, e.g. other graphs.
To do so, we add an event observer to our server
-function, which listens to what happens in the plot we just created. Then we use the information contained in the reactive value generated by the first plot (saved in input$plot_selected
) to select data shown in a second plot. In our example above, ggiraph
saves information about cars’ models in the reactive value. We can use this information to display additional information about each model selected in the first plot in a second plot.
Here’s how: In the server
-function, just below the renderGirafe()
-function for the first plot, we add the following code.
observeEvent(input$plot1_selected,{
output$plot2 <- renderGirafe({
react <- ggplot(data[which(data$model==input$plot1_selected),], aes(x=cty)) +
geom_bar() y <- girafe(ggobj = react)
y })
})
Recall that we called our first plot1
. Accordingly, ggiraph
saves reactive values in input$plot1_selected
. (Had we called our first plot rhino
, the reactive values would be stored in input$rhino_selected
).
We specify this input as a trigger in our observeEvent()
-function, and we also use it to subset our data in the initial ggplot()
call that produces the react
plot, a simple bar plot displaying information about the city miles per gallon of the model we selected in the first plot. Then we pass this plot through the girafe()
-function to produce another nice interactive plot. Finally, we add code to the UI
to display this new plot in our app.
To give the full picture, the complete code looks as follows:
library(shiny)
library(ggplot2)
library(ggiraph)# Load data
data <- mpgui <- fluidPage(
girafeOutput("plot1"),
girafeOutput("plot2")
)server <- function(input, output, session) {output$plot1 <- renderGirafe({
scatter <- ggplot(data,aes(x=displ,y=hwy)) +
geom_point_interactive(aes(tooltip=model, data_id=model)) x <- girafe(ggobj = scatter)
x }) observeEvent(input$plot1_selected,{
output$plot2 <- renderGirafe({
react <- ggplot(data[which(data$model==input$plot1_selected),],aes(x=cty)) +
geom_bar() y <- girafe(ggobj = react)
y })
})
}# Run the application
shinyApp(ui = ui, server = server)
That’s it. Now we have an app that features a plot that we can use to produce and change another plot with additional information. More complicated applications are of course also possible.
The full code to replicate this example is on GitHub.