Volcano Plot

A volcano plot visualises differential expression results by plotting log₂ fold change (x-axis) against −log₁₀(p-value) (y-axis). Points that pass both the fold-change and p-value thresholds are colored as up-regulated (right) or down-regulated (left); all others are shown as not-significant (gray). Dashed threshold lines are drawn automatically.

Import path: kuva::plot::VolcanoPlot


Basic usage

Pass (gene_name, log2fc, pvalue) tuples to .with_points(). Default thresholds are |log2FC| > 1.0 and p < 0.05.

#![allow(unused)]
fn main() {
use kuva::plot::VolcanoPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let results: Vec<(&str, f64, f64)> = vec![
    ("AKT1",   3.5, 5e-5),
    ("EGFR",   3.2, 1e-4),
    ("VHL",   -3.0, 5e-4),
    ("P21",   -3.2, 2e-4),
    ("GAPDH",  0.3, 0.50),
    // ...
];

let vp = VolcanoPlot::new()
    .with_points(results)
    .with_legend("DEG status");

let plots = vec![Plot::Volcano(vp)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Tumour vs. Normal — Volcano Plot")
    .with_x_label("log₂ fold change")
    .with_y_label("−log₁₀(p-value)");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("volcano.svg", svg).unwrap();
}
Volcano plot — tumour vs. normal

Up-regulated genes (red, right) include AKT1, EGFR, and FGFR1 at the top right. Down-regulated genes (blue, left) include P21, MLH1, and VHL. House-keeping genes (GAPDH, ACTB) and genes with large fold change but non-significant p-values (GeneC, GeneD) appear in gray. The legend shows the Up / Down / NS classification.


Gene labels

.with_label_top(n) labels the n most significant points (lowest p-values). Three placement styles are available via .with_label_style().

Nudge (default)

Labels are sorted by x position and nudged vertically to reduce stacking — the best default for most datasets.

#![allow(unused)]
fn main() {
use kuva::plot::VolcanoPlot;
use kuva::render::plots::Plot;
let vp = VolcanoPlot::new()
    .with_points(results)
    .with_label_top(12);  // LabelStyle::Nudge is the default
}
Volcano plot with nudge-style gene labels

The twelve most significant genes are labeled. Labels are spread vertically to avoid overlap while staying close to their points.

Arrow

LabelStyle::Arrow { offset_x, offset_y } moves labels by the given pixel offset and draws a short gray leader line back to the point. Useful when labels would crowd the high-significance region.

#![allow(unused)]
fn main() {
use kuva::plot::{VolcanoPlot, LabelStyle};
use kuva::render::plots::Plot;
let vp = VolcanoPlot::new()
    .with_points(results)
    .with_label_top(10)
    .with_label_style(LabelStyle::Arrow { offset_x: 14.0, offset_y: 16.0 });
}
Volcano plot with arrow-style gene labels

Each label is offset from its point by 14 px horizontally and 16 px vertically with a connecting line. This style works well for the top points in the upper corners where nudging alone may not provide enough separation.

Exact

LabelStyle::Exact places labels at the precise point position without adjustment. Labels may overlap on dense data; use this for sparse plots or when you will post-process the SVG.


Thresholds

.with_fc_cutoff(f) and .with_p_cutoff(f) set the classification thresholds. Dashed vertical lines appear at ±fc_cutoff; a dashed horizontal line appears at −log10(p_cutoff).

#![allow(unused)]
fn main() {
use kuva::plot::VolcanoPlot;
use kuva::render::plots::Plot;
let vp = VolcanoPlot::new()
    .with_points(results)
    .with_fc_cutoff(2.0)   // default 1.0 — require ≥ 4× fold change
    .with_p_cutoff(0.01)   // default 0.05 — stricter significance
    .with_label_top(8);
}
Volcano plot with stricter thresholds

With |log2FC| > 2 and p < 0.01, fewer genes pass both filters. The threshold lines move inward on the x-axis and upward on the y-axis compared to the defaults. Orange/purple colors distinguish this comparison from the default red/blue palette.


Colors

MethodDefaultDescription
.with_color_up(s)"firebrick"Points with log2FC ≥ fc_cutoff and p ≤ p_cutoff
.with_color_down(s)"steelblue"Points with log2FC ≤ −fc_cutoff and p ≤ p_cutoff
.with_color_ns(s)"#aaaaaa"All other points

All color methods accept any CSS color string.


Zero and extreme p-values

p-values of exactly 0.0 cannot be log-transformed. They are automatically clamped to the smallest non-zero p-value in the data. To set an explicit ceiling (useful when comparing multiple plots on the same y-axis scale):

#![allow(unused)]
fn main() {
use kuva::plot::VolcanoPlot;
let vp = VolcanoPlot::new()
    .with_points(results)
    .with_pvalue_floor(1e-10);  // y-axis ceiling fixed at 10
}

API reference

MethodDescription
VolcanoPlot::new()Create a plot with defaults
.with_point(name, fc, p)Add a single point
.with_points(iter)Add points from a (name, log2fc, pvalue) iterator
.with_fc_cutoff(f)|log₂FC| threshold (default 1.0)
.with_p_cutoff(f)p-value significance threshold (default 0.05)
.with_color_up(s)Up-regulated point color (default "firebrick")
.with_color_down(s)Down-regulated point color (default "steelblue")
.with_color_ns(s)Not-significant point color (default "#aaaaaa")
.with_point_size(f)Circle radius in pixels (default 3.0)
.with_label_top(n)Label the n most significant points (default 0)
.with_label_style(s)Nudge (default), Exact, or Arrow { offset_x, offset_y }
.with_pvalue_floor(f)Explicit p-value floor for −log₁₀ transform
.with_legend(s)Show an Up / Down / NS legend box