Adding BioTooltipR to R Markdown reports

library(BioTooltipR)
use_bio_tooltips(
  modules = c("gene", "chemical")
)

BioTooltipR adds small HTML spans to R Markdown output and lets the browser-side bio-tooltips JavaScript library perform the lookup and rendering. BioTooltipR vendors the required browser assets, so rendered vignettes do not need to download JavaScript or CSS at build time.

Inline prose

Genes can be included inline:

cat("The tumour suppressor ", gene_tt("TP53", species = "human"), " is central to DNA damage responses.", sep = "")

The tumour suppressor TP53 is central to DNA damage responses.

Chemicals can use stable identifiers:

cat("A chemical example: ", chem_tt("aspirin", query = "2244", scope = "pubchem"), ".", sep = "")

A chemical example: aspirin.

Tables

top_genes <- data.frame(
  symbol = c("TP53", "BRCA1", "GADD45A"),
  log2FoldChange = c(2.1, -1.4, 1.2),
  padj = c(0.0004, 0.002, 0.01)
)

top_genes |>
  gene_column(symbol, species = "human") |>
  bt_kable(include_setup = FALSE)
symbol log2FoldChange padj
TP53 2.1 4e-04
BRCA1 -1.4 2e-03
GADD45A 1.2 1e-02

The important detail is that HTML escaping must be disabled for the table cell containing tooltip spans. bt_kable() does this by default.

Assets

use_bio_tooltips() uses vendored bio-tooltips 1.1.1, D3 7.9.0, and Ideogram 1.53.0 browser assets by default, so building this vignette does not download JavaScript or CSS files. D3 and Ideogram are included automatically when the gene module is enabled because they render the gene model and chromosome ideogram. To use jsDelivr-hosted assets in a rendered report, opt in explicitly:

use_bio_tooltips(cdn = TRUE, version = "1.1.1")

Volcano plot

Interactive plotting libraries usually own their hover labels, so the most reliable pattern is to let Plotly handle the plot hover event and update a real HTML Bio Tooltip span next to the plot.

if (!requireNamespace("plotly", quietly = TRUE) ||
    !requireNamespace("htmlwidgets", quietly = TRUE)) {
  htmltools::tags$p(
    "Install the optional plotly and htmlwidgets packages to render this interactive example."
  )
} else {
set.seed(42)

volcano_genes <- c(
  "TP53", "BRCA1", "BRCA2", "EGFR", "MYC", "PTEN", "KRAS", "NRAS",
  "BRAF", "CDK1", "CCND1", "CDKN1A", "CDKN2A", "MDM2", "RB1", "ATM",
  "ATR", "CHEK1", "CHEK2", "GADD45A", "VEGFA", "HIF1A", "IL6", "TNF",
  "CXCL8", "STAT3", "JAK2", "AKT1", "MTOR", "PIK3CA", "FOXO3", "ESR1",
  "AR", "ERBB2", "MET", "ALK", "ROS1", "NTRK1", "RET", "APC",
  "CTNNB1", "SMAD4", "TGFB1", "MMP2", "MMP9", "COL1A1", "COL3A1",
  "FN1", "VIM", "CDH1", "EPCAM", "SOX2", "NANOG", "POU5F1", "MKI67",
  "AURKA", "TOP2A", "BCL2", "BAX"
)

volcano <- data.frame(
  symbol = volcano_genes,
  log2FoldChange = rnorm(length(volcano_genes), sd = 0.75),
  padj = runif(length(volcano_genes), min = 0.08, max = 0.95)
)

interesting <- data.frame(
  symbol = c("MYC", "EGFR", "VEGFA", "IL6", "MMP9", "MKI67",
             "TP53", "BRCA1", "CDKN1A", "PTEN", "CDH1", "BAX"),
  log2FoldChange = c(2.6, 1.9, 1.7, 2.2, 1.5, 2.9,
                     -2.1, -1.8, -1.4, -1.7, -2.4, -1.3),
  padj = c(0.00012, 0.0018, 0.006, 0.0009, 0.014, 0.00004,
           0.0007, 0.003, 0.018, 0.009, 0.0002, 0.022)
)

volcano[match(interesting$symbol, volcano$symbol), c("log2FoldChange", "padj")] <-
  interesting[c("log2FoldChange", "padj")]

volcano$neg_log10_padj <- -log10(volcano$padj)
volcano$direction <- ifelse(
  volcano$padj < 0.05 & volcano$log2FoldChange >= 1,
  "Up-regulated",
  ifelse(
    volcano$padj < 0.05 & volcano$log2FoldChange <= -1,
    "Down-regulated",
    "Not significant"
  )
)
volcano$direction <- factor(
  volcano$direction,
  levels = c("Up-regulated", "Down-regulated", "Not significant")
)
volcano$label <- ifelse(volcano$symbol %in% interesting$symbol, volcano$symbol, "")

volcano_plot <- plotly::plot_ly(
  volcano,
  x = ~log2FoldChange,
  y = ~neg_log10_padj,
  key = ~symbol,
  type = "scatter",
  mode = "markers+text",
  text = ~label,
  textposition = "top center",
  color = ~direction,
  colors = c(
    "Up-regulated" = "#b91c1c",
    "Down-regulated" = "#2563eb",
    "Not significant" = "#7a7f87"
  ),
  marker = list(size = 9, opacity = 0.82, line = list(width = 0)),
  textfont = list(size = 10, color = "#24292f")
) |>
  plotly::layout(
    xaxis = list(title = "log2 fold change", zeroline = TRUE),
    yaxis = list(title = "-log10 adjusted p-value"),
    legend = list(orientation = "h", x = 0, y = 1.12),
    margin = list(t = 60),
    shapes = list(
      list(type = "line", x0 = -1, x1 = -1, y0 = 0, y1 = 4.6,
           line = list(color = "#b7bdc5", width = 1, dash = "dot")),
      list(type = "line", x0 = 1, x1 = 1, y0 = 0, y1 = 4.6,
           line = list(color = "#b7bdc5", width = 1, dash = "dot")),
      list(type = "line", x0 = -3.2, x1 = 3.2, y0 = -log10(0.05), y1 = -log10(0.05),
           line = list(color = "#b7bdc5", width = 1, dash = "dot"))
    )
  )

bt_plotly_gene_hover(volcano_plot, species = "human", include_setup = FALSE)
}

Plotly still owns the hover event, but its default hover card is suppressed. bt_plotly_gene_hover() reads the gene symbol from Plotly’s key field and updates a tiny fixed-position Bio Tooltip span at the cursor.

Experimental auto-linking

If prose is already rendered, auto_gene_tooltips() can wrap a supplied vocabulary of gene symbols in a constrained selector. This should be used carefully because some gene symbols are ordinary words.

The following sentence is ordinary Markdown rather than a collection of calls to gene_tt(). Hover over a mouse gene symbol to see the auto-linking in action.

auto_gene_tooltips(
  genes = c("Trp53", "Brca1", "Gadd45a"),
  species = "mouse",
  selector = ".auto-link-demo",
  include_setup = FALSE
)

Using a dedicated selector keeps the scan inside this example. The mouse-specific vocabulary also avoids matching the human gene symbols used in earlier sections of the vignette.