--- title: "CanvasXpress visualizations for Bioconductor data" author: "Isaac Neuhaus" package: CanvasXpressBio output: BiocStyle::html_document: toc: true vignette: > %\VignetteIndexEntry{CanvasXpress visualizations for Bioconductor data} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` # Introduction `CanvasXpressBio` connects the [CanvasXpress](https://canvasxpress.org) interactive visualization library to core Bioconductor data structures. It does not replace the CRAN `canvasXpress` package — it adds methods that take a Bioconductor object directly and render it as a reproducible CanvasXpress widget, carrying the object's annotations along for interactive grouping, coloring and sorting. This vignette builds **one** `SummarizedExperiment` and shows it four ways — heatmap, volcano plot, Kaplan-Meier curve and paired boxplot — so the same samples and genes run through every figure. ```{r load, message = FALSE} library(CanvasXpressBio) library(SummarizedExperiment) ``` ## Why CanvasXpressBio? Bioconductor already has excellent visualization packages. Static heatmaps come from `ComplexHeatmap` and `pheatmap`; `EnhancedVolcano` draws volcano plots; `survminer` draws Kaplan-Meier curves; and `InteractiveComplexHeatmap` adds interactivity to a `ComplexHeatmap` — but through a Shiny application, which needs a running R server to explore the figure. `CanvasXpressBio` is complementary rather than a replacement, and is useful when: - **You want interactivity without a server.** Each call returns a self-contained CanvasXpress htmlwidget: hover for values, zoom, reorder and toggle groups all work in a plain knitted vignette, an R Markdown report, a Quarto page or an email — no Shiny runtime. `InteractiveComplexHeatmap` is the better choice inside a Shiny app; `CanvasXpressBio` is the better choice for a standalone, shareable HTML artefact. - **You want one grammar across many figure types.** The same `SummarizedExperiment` drives the heatmap, volcano, survival curve and boxplot below through four small functions, so the `colData` / `rowData` annotations are reused consistently instead of being reshaped for a different package per plot. - **You want the object's metadata to travel automatically.** `colData` becomes sample overlays and `rowData` becomes feature overlays, so cohort, time point and gene direction are available for interactive colouring and sorting without extra bookkeeping. The rest of this vignette demonstrates these four views on a single object. # One dataset for every figure The study follows 24 patients in two cohorts, each measured at baseline and at week 12, over a panel of twelve marker genes. The `colData` records the cohort, the time point, the subject, a follow-up time with an event indicator, and whether that subject's biomarker went up or down; the `rowData` records whether each gene is an up- or down-regulated marker. To keep the figures below uncluttered, the data construction lives in a single helper, `makeDemoSE()`, so the vignette builds the whole object with one call. The derived pieces the other figures need — a differential-expression table for the volcano and the per-cohort paired t-test p-values for the boxplot — travel with the object in its `metadata()`. (See `?makeDemoSE` for exactly how the object is assembled.) ```{r dataset} se <- makeDemoSE() se ``` # A heatmap from a SummarizedExperiment `cxplot()` turns the assay matrix into an interactive heatmap. `colData` becomes sample (column) annotations and `rowData` becomes feature (row) annotations, so the cohort, time point and gene direction travel with the plot. `overlays` and `var_overlays` draw chosen annotations as coloured tracks beside the map, and `cluster = TRUE` clusters the rows and columns with dendrograms. These are the same twelve genes highlighted in the volcano plot below. ```{r heatmap} cxplot(se, title = "Marker-gene expression", overlays = c("Cohort", "Condition"), var_overlays = "Direction", cluster = TRUE) ``` **Enriching the hover metadata.** The annotations are not only drawn as the coloured tracks beside the map — they also travel into every cell's tooltip. Hovering a cell shows its expression value together with the gene and sample it belongs to and the `colData` / `rowData` fields listed in `overlays` / `var_overlays` (here the cohort, time point and gene direction). To surface more metadata on hover, a user simply adds more columns to the object's `colData` or `rowData` and names them in `overlays` / `var_overlays` — no extra wiring. **Resetting the view.** After zooming (mouse wheel) or a two-finger pinch, the toolbar's reset button — and a double-click anywhere on the plot — restores the original view, the same role Plotly's *home* control plays. Because `SingleCellExperiment` and `DESeqDataSet` both extend `SummarizedExperiment`, the same method renders them without any extra code. # A volcano plot from differential-expression results `cxvolcano()` accepts any result table with a log-fold-change and a p-value column — a `DESeq2::results()` object, a `limma::topTable()` data frame, or a plain data frame. Here week 12 is contrasted against baseline; the twelve marker genes from the heatmap are the labelled hits. The result table (the twelve markers among a genome-wide background) was computed in `makeDemoSE()` and rides along in the object's `metadata()`. ```{r volcano} res <- S4Vectors::metadata(se)$deResults cxvolcano(res, title = "Week 12 vs baseline", label_genes = rownames(se)) ``` # A Kaplan-Meier survival curve `cxsurvival()` reads the follow-up time and event status straight from `colData`. Using the baseline rows gives one record per patient, stratified by cohort. The curve is interactive, and the interactivity carries the per-subject information: `tooltip` names extra `colData` columns to attach to each subject, so hovering a step or a censored observation shows *that patient's* metadata. Censored observations are drawn as crosshair marks on the curve and, with `kmCensoredRug = TRUE`, as a rug along the time axis; the risk table ("Subjects at Risk") and the log-rank p-value are shown as well. ```{r km} baseline_se <- se[, se$Condition == "Baseline"] cxsurvival(baseline_se, time = "Time", status = "Status", group = "Cohort", tooltip = c("Patient", "updown"), kmCensoredRug = TRUE, kmPvalue = TRUE, title = "Survival by cohort") ``` # A paired boxplot with per-patient connections `cxboxplot()` plots one marker across the two time points, draws each patient's points, and joins the repeated measures with a line coloured by the direction of change — faceted by cohort. The per-cohort paired t-test p-values were computed in `makeDemoSE()` and read back from `metadata()` to label each panel. ```{r boxplot} paired <- S4Vectors::metadata(se)$albPaired ptext <- function(p) if (p < 1e-4) "p<0.0001" else sprintf("p=%.3g", p) ytop <- max(assay(se)["ALB", ]) + 0.3 cxboxplot(se, feature = "ALB", group = "Condition", segregate = "Cohort", connect = "Patient", color = "updown", title = "ALB over time, per patient", colorKey = list(updown = list( up = "rgb(224,130,20)", down = "rgb(58,102,160)")), decorations = list(text = list( list(value = ytop, label = ptext(paired[["Cohort A"]]), panel = 1, smpCenter = TRUE, preTransformed = TRUE), list(value = ytop, label = ptext(paired[["Cohort B"]]), panel = 2, smpCenter = TRUE, preTransformed = TRUE)))) ``` ## Beeswarm overlays at scale The paired boxplot above draws every observation because the design is small. When a group holds many points, the `points` argument switches the overlay to a sina / beeswarm layout that packs the points inside the distribution envelope rather than overplotting. `makeBeeswarmDemo()` returns a single-feature object with 500 observations in each of five groups (2,500 points); `points = "sina"` keeps it legible. ```{r beeswarm} bse <- makeBeeswarmDemo(n_per_group = 500) cxboxplot(bse, feature = "Expression", group = "Group", color = "Group", points = "sina", showLegend = FALSE, title = "Beeswarm overlay with 2,500 points") ``` # Session info ```{r sessioninfo} sessionInfo() ```