plotomics

GPU-accelerated bioinformatics visualization for R, Python and the web.

plotomics is a set of visualization widgets built on one TypeScript rendering core, wrapped for R with htmlwidgets and for Python with anywidget. Numeric data is sent to the browser as a binary buffer instead of JSON, so plots stay interactive at hundreds of thousands to millions of points. The same figure works in a Jupyter notebook, a Shiny app, the RStudio Viewer, or a plain web page.

Why

Omics data outgrew the plotting stack. A 500k-cell embedding or a 20k-gene volcano is routine now, but the tools most of these figures are drawn with were designed for a few thousand marks, and past that the figure stops being readable before the data stops being interesting. Separately, labs run on both R and Python, so the same figure gets implemented twice and the two drift apart.

plotomics writes each component once and wraps it thinly for both languages, so the notebook and the paper agree. It is deliberately not a general plotting library, and for a small static figure there are better tools. Read the motivation for the goals, the non-goals, how it compares with ComplexHeatmap, maftools, survminer, Seurat and scanpy, and when to use one of those instead.


Components

Each component is a headless factory that runs the same way in a notebook, a Shiny app, or a web page. The R and Python wrappers stay thin because the rendering logic lives in the shared core.

ComponentFigure it producesRendering engineData scale
Volcano plotVolcano: log2 fold change against −log10 p, with threshold guides and top-N gene labels.regl-scatterplot (WebGL point sprites), d3-scale, d3-array200k points in the dev harness
Expression heatmapSample by gene expression matrix, optional row z-score, colorbar.regl (WebGL; the matrix is uploaded as a float texture and sampled in a shader)1000 × 1000 = 1M cells
Clustered heatmapClustered heatmap with row and column dendrograms. The seaborn.clustermap / Morpheus layout.Canvas 2-D ImageData, one pixel per cell then scaled; ml-hclust for the linkage; dendrograms in SVG120 × 60 in the dev harness
Marker gene dot plotMarker gene dot plot. The equivalent of scanpy sc.pl.dotplot or Seurat DotPlot().Canvas 2-D, SVG overlayPanel scale: features × groups
Stacked violinStacked violin panel. The equivalent of scanpy sc.pl.stacked_violin or Seurat VlnPlot().Canvas 2-D, SVG overlay. Densities are estimated upstream, in R or PythonPayload scales with the density grid, not the cell count
EmbeddingUMAP, t-SNE and PCA score plots, coloured categorically or continuously, with lasso selection.regl-scatterplot (WebGL point sprites, one draw call), d3-scale150k in the dev harness, 500k in the README example
Spatial tissue mapVisium-style spot map drawn over the H&E section, coloured by cluster or by a gene.Canvas 2-D, one arc per spot; image and spots share a single computed fitVisium scale, ~4k spots in the dev harness. Not the path for Xenium-scale single cells, see the note below
Oncoplot (OncoPrint)OncoPrint / oncoplot: gene by sample alteration classes, burden and frequency margins, clinical strips. The maftools / cBioPortal figure.Canvas 2-D for the whole grid, SVG for labels and legend60 genes × 1,200 samples = 72k cells
Protein domain lollipopProtein domain lollipop, also called a mutation needle plot, over Pfam/InterPro domain rectangles.Canvas 2-D, SVG overlayHundreds to a few thousand variants
Kaplan-Meier curveKaplan-Meier survival curves with censoring ticks, confidence bands and a number-at-risk table.Canvas 2-D, SVG overlay. The fit comes from survival or lifelinesA handful of strata
Categorical profileSBS96 mutational signature profile: 96 trinucleotide contexts under six substitution-class header blocks.Canvas 2-D, SVG overlay96 contexts, generalises to a few thousand bins
UpSet plotUpSet plot: intersection sizes as bars over a set-membership matrix, for where a Venn diagram is no longer drawable.Canvas 2-D, SVG overlay. Exclusive intersections are computed in RDozens of sets
Gene treemapTreemap of gene sets and pathway composition, coloured by parent or by value.Canvas 2-D + d3-hierarchy (squarified treemap layout)60k leaves in the dev harness
Network graphForce-directed biological networks: protein interaction, co-expression, regulatory graphs.sigma v3 (WebGL) + graphology + graphology-layout-forceatlas25k nodes in the dev harness
Hi-C contact matrixHi-C and Micro-C contact maps, log or linear intensity, symmetric fill.regl (WebGL) with its own level-of-detail pyramid. No tile server, and no HiGlass1024 × 1024 ≈ 1M bins
Genome viewerTrack-based genome browser: BAM alignments, BigWig coverage, VCF, BED, refGene.igv.js, which streams and tiles internallyWhole genome, streamed from the server
Genomics figuresDeclarative genomics figures from a Gosling spec: circos, ideograms, linked and stacked views.gosling.js on PIXI.js (WebGL)Whole genome, per the spec

On “data scale”. These are the synthetic sizes the dev harness actually drives, not benchmark results. They say what each component is built and exercised for, not where it stops.

Worked example: a million cells

Take a 1M-cell Xenium run. The cell centroids go through the embedding component, which is regl-scatterplot: points become WebGL sprites drawn from GPU buffers rather than one node per point, and the coordinates arrive from Python as a Float32Array buffer rather than JSON. The x and y for a million cells is two million float32s, so 8 MB of binary the browser hands straight to the GPU, against roughly 40 MB of JSON text it would otherwise parse first. Colour by cluster, lasso a region, and read the selected indices back in Shiny.

What you do not get on that path is the H&E underlay. The spatial component is the one that puts measurements on the histology image, and it draws one canvas arc per spot, which is sized for Visium-scale slides of a few thousand spots. Registering a million single cells to the image is not something it does today. That is a real limitation and worth knowing before you plan a figure around it.

Could plotly not do this?

For the scatter, largely yes, and it is worth being straight about that. plotly.js has a WebGL trace, scattergl, and it will draw a million points. Anyone who tells you plotly cannot handle large scatter is describing the default SVG scatter trace, which emits one DOM node per point and dies in the low tens of thousands. The real differences are narrower and more boring:

Against that, plotly is far more general, much more mature, and better documented. If you need a chart that is not one of the seventeen above, it is the better tool, and the motivation doc is explicit about the other cases where an established package should win.

Installation

R

install.packages("plotomics",
                 repos = "https://samuelbharti.r-universe.dev")

Load it with library(plotomics). Widgets also render outside Shiny, in the RStudio Viewer, R Markdown, and Quarto.

Python

pip install plotomics

Works in Jupyter, JupyterLab, Colab, VS Code, marimo, Shiny for Python, and Streamlit.

JavaScript

pnpm add plotomics

Headless factories with tree-shakeable subpath imports, for example plotomics/embedding.

The registries are not populated until the first tagged release. plotomics ships through r-universe, PyPI, and npm. Until then, install from source. See the README.

How it works

Documentation

The R and Python reference sites are generated by the Docs workflow (pkgdown and pdoc). Run that workflow from the Actions tab to publish them to GitHub Pages.

Package registries