Interactive Plots in Shiny Apps

Matt Eldridge

Last modified: 13 Sep 2017

Introduction

In this section we briefly introduce creating Shiny applications with interactive plots. These are figures where clicking on a point within a plot will trigger a selection event that causes an update to another part of the application.

We will show how this can be achieved with static plots generated by ggplot2 and also with two JavaScript libraries, Plotly and HighCharts, which allow for even more interactivity including zooming and tooltips.

For the examples shown here we use the results of a differential expression analysis using limma, comparing ER negative and positive status groups (see DE.R script).

ggplot2

The following Shiny application shows a Volcano plot of the log P-value versus the log fold change.

It is possible when using ggplot2 (and base) graphics to handle mouse click events within a Shiny application. This can be very useful for allowing a user to select data points of interest and display more detailed information about the items selected.

Here we add a click argument to plotOutput to listen to mouse click events and update a table with details of the data points near to the cursor. You can also listen to double-click and hover (mouse-over) events in exactly the same way. Note the way in which the table is updated in response to a mouse click as reflected in a change in the value of input$volcanoPlotClick.

nearPoints is a convenient function for extracting the rows of the data frame on which the plot is based for the points near where the mouse was clicked. In the code below, nearPoints is used within the renderDataTable function to create the data frame that populates the table.

library(shiny)
library(tidyverse)

ui <- fluidPage(
  titlePanel("Volcano Plot"),
  fluidRow(
    column(
      width = 6,
      plotOutput("volcanoPlot", click = "volcanoPlotSelection", height = "500px")
    ),
    column(
      width = 6,
      dataTableOutput("selectedProbesTable")
    )
  )
)

server <- function(input, output) {

  differentialExpressionResults <-
    read.csv("NKI-DE-results.csv", stringsAsFactors = FALSE) %>%
    mutate(
      probe.type = factor(ifelse(grepl("^Contig", probe), "EST", "mRNA")),
      minusLog10Pvalue = -log10(adj.P.Val)
    )

  output$volcanoPlot <- renderPlot({

    ggplot(differentialExpressionResults, aes(x = logFC, y = minusLog10Pvalue, colour = probe.type)) +
      geom_point() +
      xlab("log fold change") +
      ylab("-log10(P-value)") +
      theme(legend.position = "bottom")
  })

  output$selectedProbesTable <- renderDataTable(

    nearPoints(differentialExpressionResults, input$volcanoPlotSelection) %>%
      transmute(
        probe,
        gene = HUGO.gene.symbol,
        `log fold change` = signif(logFC, digits = 2),
        `p-value` = signif(adj.P.Val, digits = 2)
      ),

    options = list(dom = "tip", pageLength = 10, searching = FALSE)
  )
}

shinyApp(ui, server, options = list(height = 600))

Note that if you are using base graphics the nearPoints function needs to be provided with details of what columns were used for the x- and y-axes using the xvar and yvar arguments; for ggplot2 Shiny was able to infer these.

Exercise

Try changing the code above to handle mouse-over (hover) and brush events (brushing involves drawing a box in the plot area) instead of mouse clicks (hint: see help page for plotOutput function).

Plotly

Plotly is an R package for creating interactive web-based graphs via the open source JavaScript graphing library plotly.js (which in turn is built on D3.js). Although developed by a company of the same name, it is open source.

One really great feature of the plotly R package is the ability to convert a ggplot figure into an interactive plot using the ggplotly function. This enables the resulting plot to be even more interactive with features such as zooming, panning, tooltips, toggling the display of subsets of data by clicking on legend items, and a save as PNG option.

In the following example, we’ve changed the above Shiny application to use ggplotly. In this case, selection is facilitated by drawing a box around data points of interest (‘brushing’). Handling of click, hover and relayout (e.g. zoom) events are also supported.

library(shiny)
library(plotly)
## Warning: package 'plotly' was built under R version 3.4.1
library(tidyverse)

ui <- fluidPage(
  titlePanel("Volcano Plotly"),
  fluidRow(
    column(
      width = 7,
      plotlyOutput("volcanoPlot", height = "500px")
    ),
    column(
      width = 5,
      dataTableOutput("selectedProbesTable")
    )
  )
)

server <- function(input, output) {

  differentialExpressionResults <-
    read.csv("NKI-DE-results.csv", stringsAsFactors = FALSE) %>%
    mutate(
      probe.type = factor(ifelse(grepl("^Contig", probe), "EST", "mRNA")),
      minusLog10Pvalue = -log10(adj.P.Val),
      tooltip = ifelse(is.na(HUGO.gene.symbol), probe, paste(HUGO.gene.symbol, " (", probe, ")", sep = ""))
    ) %>%
    sample_n(1000)

  output$volcanoPlot <- renderPlotly({

    plot <- differentialExpressionResults %>%
      ggplot(aes(x = logFC,
                 y = minusLog10Pvalue,
                 colour = probe.type,
                 text = tooltip,
                 key = row.names(differentialExpressionResults))) +
      geom_point() +
      xlab("log fold change") +
      ylab("-log10(P-value)")

    plot %>%
      ggplotly(tooltip = "tooltip") %>%
      layout(dragmode = "select")
  })

  output$selectedProbesTable <- renderDataTable({

    eventData <- event_data("plotly_selected")

    selectedData <- differentialExpressionResults %>% slice(0)
    if (!is.null(eventData)) selectedData <- differentialExpressionResults[eventData$key,]

    selectedData %>%
      transmute(
        probe,
        gene = HUGO.gene.symbol,
        `log fold change` = signif(logFC, digits = 2),
        `p-value` = signif(adj.P.Val, digits = 2)
      )
  },
    options = list(dom = "tip", pageLength = 10, searching = FALSE)
  )
}

shinyApp(ui, server, options = list(height = 600))

As with other client-side JavaScript libraries, the processing required for the interactivity is being handled within the web browser. Plots containing many thousands of data points can become quite unresponsive. Here we sampled 1000 points randomly from the 25,000 in our original data frame. In practice it might make more sense to partition the data into a subset that needs to be displayed, e.g. genes with either low p-value or high fold changes, and a subset from which data points can be sampled to make up a manageable data set for including in the plot.

HighCharts using highcharter R wrapper

HighCharts is a popular commercial interactive JavaScript charting library. It is free for non-commercial use. There are at least two R wrapper packages for HighCharts. Here we use the highcharter package.

The array of configuration parameters for HighCharts is somewhat bewildering, providing control over almost all aspects of plotting. The documentation is comprehensive, at least that is if you’re programming directly in JavaScript. The refernce manual for the highcharter package has improved considerably but there is still quite a steep learning curve involved in using the package and working out how to specify a configuration parameter is not always straightforward.

Event handling for tooltips and mouse clicks is also more complicated and involves creating small JavaScript functions.

library(shiny)
library(tidyverse)
library(highcharter)

ui <- fluidPage(
  titlePanel("Volcano HighChart"),
  fluidRow(
    column(
      width = 7,
      highchartOutput("volcanoPlot", height = "500px")
    ),
    column(
      width = 5,
      dataTableOutput("selectedProbesTable")
    )
  )
)

server <- function(input, output) {

  differentialExpressionResults <-
    read.csv("NKI-DE-results.csv", stringsAsFactors = FALSE) %>%
    mutate(
      probe.type = factor(ifelse(grepl("^Contig", probe), "EST", "mRNA")),
      minusLog10Pvalue = -log10(adj.P.Val),
      tooltip = ifelse(is.na(HUGO.gene.symbol), probe, paste(HUGO.gene.symbol, " (", probe, ")", sep = ""))
    ) %>%
    sample_n(1000)

  output$volcanoPlot <- renderHighchart({

    plot <- highchart() %>%
      hc_chart(animation = TRUE, zoomType = "xy") %>%
      hc_xAxis(title = list(text = "log fold change")) %>%
      hc_yAxis(title = list(text = "-log10(P-value)")) %>%
      hc_colors(c('rgba(67, 67, 72, 0.6)', 'rgba(124, 181, 236, 0.6)')) %>%
      hc_legend(layout = "horizontal", align = "center", verticalAlign = "bottom") %>%
      hc_exporting(enabled = TRUE, filename = "volcanoPlot.svg") %>%
      hc_tooltip(
        animation = FALSE,
        formatter = JS("function() { return (this.point.tooltip) }")) %>%
      hc_plotOptions(series = list(
        cursor = "pointer",
        point = list(events = list(click = JS("function() { Shiny.onInputChange('volcanoPlot_clicked', this.id) }")))))

    for (probeType in levels(differentialExpressionResults$probe.type)) {

      seriesData <- differentialExpressionResults %>%
        filter(probe.type == probeType) %>%
        select(id = probe, x = logFC, y = minusLog10Pvalue, tooltip) %>%
        list_parse

      plot <- plot %>%
        hc_add_series(
          data = seriesData,
          name = probeType,
          type = "scatter",
          marker = list(symbol = "circle"),
          stickyTracking = FALSE
        )
    }

    plot
  })

  output$selectedProbesTable <- renderDataTable({

    selectedData <- differentialExpressionResults %>% slice(0)

    clicked <- input$volcanoPlot_clicked
    if (!is.null(clicked))
      selectedData <- differentialExpressionResults %>% filter(probe == clicked)

    selectedData %>%
      transmute(
        probe,
        gene = HUGO.gene.symbol,
        `log fold change` = signif(logFC, digits = 2),
        `p-value` = signif(adj.P.Val, digits = 2)
      )
  },
    options = list(dom = "t", searching = FALSE)
  )
}

shinyApp(ui, server, options = list(height = 575))

Other packages

Several interactive visualization packages that use JavaScript libraries can be used within Shiny applications. These include:

Several example shiny applications that showcase these and other interactive visualization packages can be found here: