install.packages("canvasXpress")Interactive volcano plots in R with canvasXpress
A volcano plot is the workhorse of differential-expression analysis: log fold change on x, statistical significance on y, and the interesting genes pushed out to the top corners. In this post we build one in R with canvasXpress — interactive (zoom, hover, filter), and, importantly, reproducible: the whole figure is a single portable specification you can store, validate and re-render from R, Python or JavaScript.
Install
A differential-expression table
For a self-contained example we simulate 1,000 genes. Swap this block for your own results table (e.g. the output of limma::topTable() or DESeq2::results()) — you need a log fold change and a significance value per gene.
library(canvasXpress)
set.seed(1)
n <- 1000
genes <- paste0("Gene", seq_len(n))
# Simulate a realistic volcano: most genes have no real effect, a minority are
# differentially expressed. Significance follows effect size / standard error, which
# is what gives a volcano its shape.
de <- runif(n) < 0.12 # ~12% truly changed
sgn <- sample(c(-1, 1), n, replace = TRUE)
effect <- ifelse(de, sgn * (0.8 + 0.9 * runif(n)), 0) # true log2FC
se <- 0.22 + 0.5 * runif(n) # per-gene standard error
logFC <- round(effect + rnorm(n, 0, se), 3) # observed log2FC
pval <- 2 * pnorm(-abs(logFC / se)) # z-test p-value
negLogP <- round(pmin(-log10(pval), 16), 3)
# Classify each gene by effect size + significance
group <- ifelse(logFC > 1 & negLogP > 1.3, "Increased",
ifelse(logFC < -1 & negLogP > 1.3, "Decreased", "NoChange"))canvasXpress expects a numeric matrix/data frame of values (genes in rows) plus an optional variable annotation table carrying per-gene attributes — here the regulation call and the fold change we’ll use for point size.
y <- data.frame(
"logFC" = logFC,
"-log-pVal" = negLogP,
row.names = genes,
check.names = FALSE
)
varAnnot <- data.frame(
Group = group,
FC = round(2^abs(logFC), 2),
row.names = genes,
check.names = FALSE
)The plot
canvasXpress(
data = y,
varAnnot = varAnnot,
graphType = "Scatter2D",
xAxis = list("logFC"),
yAxis = list("-log-pVal"),
colorBy = "Group",
colorKey = list(Group = list(
Increased = "rgba(197,27,38,0.75)",
Decreased = "rgba(33,102,172,0.75)",
NoChange = "rgba(150,150,150,0.35)"
)),
sizeBy = "FC",
sizes = c(3, 6, 9, 12, 15),
title = "Publication-ready volcano plot",
xAxisTitle = "log2 fold change",
yAxisTitle = "-log10 p-value",
showDecorations = TRUE,
decorations = list(line = list(
list(color = "rgba(120,120,120,0.8)", width = 1, x = 1),
list(color = "rgba(120,120,120,0.8)", width = 1, x = -1),
list(color = "rgba(120,120,120,0.8)", width = 1, y = 1.301)
)),
width = 650,
height = 500
)The chart is interactive: hover a point for its gene and values, drag to zoom, or use the toolbar to filter by regulation group. The dashed guides mark the ±1 log2FC and the p = 0.05 cutoffs.
Why “reproducible”
Everything above serializes to one JSON specification — the data and the config. That spec is portable: the identical figure renders from the Python (canvasxpress) and JavaScript packages, and it can be validated against a published schema and re-rendered later, so a figure in a paper can be regenerated and audited rather than just looked at.
Where to go next
- Gallery of examples: https://canvasxpress.org/examples.html
- From your object (survival tables, expression matrices, single-cell): https://canvasxpress.org/migrate.html
- Package & issues: https://github.com/neuhausi/canvasXpress