There are a wide range of packages for working for scRNA-Seq data in R, but it sometimes feels like we are working with the data at arm’s length. In this vignette, I would like to demonstrate some interactive features spanning several R packages that allow more direct engagement with data using HTML widgets. My selfish hope is that more people will use these features, and in the process work out some of the rough edges and develop more tools in this space.

The key package I want to demonstrate is crosstalk which allows selections and filters to be shared between widgets. A number of packages support crosstalk, notably including plotly for plots and DT for tables. I will also demonstrate my own langevitour package for exploring high-dimensional data.

These widgets work in a static HTML page, which can be created using rmarkdown and shared as a file with collaborators or put on the web. A shiny server is not required.

## 2022-11-07 This code requires the development versions of langevitour and plotly.R
# remotes::install_github("pfh/langevitour")
# remotes::install_github("plotly/plotly.R")

library(Seurat)
library(ggbeeswarm)
library(crosstalk)    # SharedData objects allow widgets to share selections and filters
library(ggplot2)      # Can use SharedData object in place of data frame
library(plotly)       # Can convert ggplots into widgets
library(DT)           # Table widget, can use SharedData object
library(langevitour)  # Tour high-dimensional data, can link with SharedData object 
library(htmltools)    # Create HTML, lay out widgets

# Use the Seurat object created during the workshop
pbmc <- readRDS("pbmc3k_final.rds")


# plotly's crosstalk implementation requires a little tweaking to work right.
plotlify <- function(plot) {
    ggplotly(plot, tooltip="text") |> 
        style(unselected=list(marker=list(opacity=1))) |> # Don't double-fade unselected points
        highlight(on="plotly_selected", off="plotly_deselect")
}

# A data frame with the data for most of the widgets we will show
df <- cbind(
        pbmc@meta.data[,c("nCount_RNA","nFeature_RNA","percent.mt")], 
        ident=Idents(pbmc), 
        pbmc$umap@cell.embeddings)
 
# Turning a data frame into a crosstalk SharedData object allows widgets to communicate
shared <- SharedData$new(df)


# The SharedData object is used instead of the data frame
p_count <- ggplot(shared) + 
    aes(x="PBMC", y=nCount_RNA) + 
    geom_beeswarm(size=0.5, cex=0.75) + 
    labs(x="")

p_feature <- ggplot(shared) + 
    aes(x="PBMC", y=nFeature_RNA) + 
    geom_beeswarm(size=0.5, cex=0.75) + 
    labs(x="")

p_mt <- ggplot(shared) + 
    aes(x="PBMC", y=percent.mt) + 
    geom_beeswarm(size=0.5, cex=0.75) + 
    labs(x="")

colors <- scales::hue_pal()(9)

p_umap <- ggplot(shared) + 
    aes(x=UMAP_1, y=UMAP_2, color=ident) + 
    geom_point(size=0.5) + 
    scale_color_manual(values=colors) + 
    coord_fixed()

# A table widget, again using the SharedData object
w_table <- datatable(
    shared,
    width="100%", class='compact cell-border hover', extensions='Buttons',
    options=list(dom='Bfrtip',buttons=c('copy','csv','excel')))

# A high-dimensional tour widget
w_langevitour <- langevitour(
    pbmc$pca@cell.embeddings[,1:8], Idents(pbmc), 
    levelColors=colors, pointSize=1.5, link=shared,
    width=800, height=500)

f_counts <- filter_slider("f_counts", "nCount_RNA filter", shared, "nCount_RNA") 

# Arrange everything in a nice layout using HTML
browsable(div(
    f_counts,
    div(style="display: grid; grid-template-columns: 150px 150px 150px 500px;", 
        plotlify(p_count), 
        plotlify(p_feature), 
        plotlify(p_mt),
        plotlify(p_umap)),
    w_langevitour,
    w_table))

Things to try:

Things to notice: