Manhattan Plot
A Manhattan plot displays GWAS p-values across the genome. Each point represents a SNP; the x-axis spans chromosomes and the y-axis shows −log₁₀(p-value). Chromosomes are colored with an alternating scheme. Dashed threshold lines are drawn automatically at the genome-wide (p = 5×10⁻⁸) and suggestive (p = 1×10⁻⁵) significance levels.
Import path: kuva::plot::ManhattanPlot
Basic usage — sequential mode
When base-pair positions are unavailable, pass (chrom, pvalue) pairs to .with_data(). Chromosomes are sorted in standard genomic order (1–22, X, Y, MT); points within each chromosome receive consecutive integer x positions.
#![allow(unused)] fn main() { use kuva::plot::ManhattanPlot; use kuva::backend::svg::SvgBackend; use kuva::render::render::render_multiple; use kuva::render::layout::Layout; use kuva::render::plots::Plot; let data: Vec<(String, f64)> = vec![ ("1".into(), 0.42), ("1".into(), 3e-8), ("3".into(), 2e-9), ("6".into(), 5e-6), // ... ]; let mp = ManhattanPlot::new() .with_data(data) .with_legend("GWAS thresholds"); let plots = vec![Plot::Manhattan(mp)]; let layout = Layout::auto_from_plots(&plots) .with_title("GWAS — Sequential x-coordinates") .with_y_label("−log₁₀(p-value)"); let svg = SvgBackend.render_scene(&render_multiple(plots, layout)); std::fs::write("manhattan.svg", svg).unwrap(); }
All 23 chromosomes appear. Points in the significant and suggestive regions are visible above the dashed threshold lines. The legend shows both threshold entries.
Base-pair mode — GRCh38
.with_data_bp(data, GenomeBuild::Hg38) maps each (chrom, bp, pvalue) triplet onto a true genomic x-axis. All chromosomes in the build appear as labeled bands even if they contain no data points.
#![allow(unused)] fn main() { use kuva::plot::{ManhattanPlot, GenomeBuild}; use kuva::render::plots::Plot; // (chrom, base-pair position, pvalue) from PLINK/GCTA output let data: Vec<(String, f64, f64)> = vec![]; // ... let mp = ManhattanPlot::new() .with_data_bp(data, GenomeBuild::Hg38) .with_label_top(10) .with_legend("GWAS thresholds"); }
The x-axis now reflects true chromosomal proportions. The 10 most significant hits above the genome-wide threshold are labeled. Chromosome names are accepted with or without the "chr" prefix.
Available builds:
| Variant | Assembly | Chromosomes |
|---|---|---|
GenomeBuild::Hg19 | GRCh37 / hg19 | 1–22, X, Y, MT |
GenomeBuild::Hg38 | GRCh38 / hg38 | 1–22, X, Y, MT |
GenomeBuild::T2T | T2T-CHM13 v2.0 | 1–22, X, Y, MT |
GenomeBuild::Custom(…) | User-defined | Any |
Gene-name labels with with_point_labels
.with_point_labels(iter) attaches specific gene or SNP names to individual points by (chrom, x, label) triplets. Combine this with .with_data_x() (pre-computed x positions) for exact matching.
#![allow(unused)] fn main() { use kuva::plot::{ManhattanPlot, LabelStyle}; use kuva::render::plots::Plot; let data: Vec<(&str, f64, f64)> = vec![ // chr1 — BRCA2 locus ("1", 10.0, 0.42), ("1", 40.0, 2e-10), ("1", 60.0, 0.09), // chr2 — TP53 locus ("2", 120.0, 0.71), ("2", 140.0, 5e-9), ("2", 160.0, 0.13), // chr3 — EGFR locus ("3", 220.0, 0.62), ("3", 250.0, 1e-9), ("3", 270.0, 0.51), ]; let mp = ManhattanPlot::new() .with_data_x(data) .with_label_top(5) .with_label_style(LabelStyle::Arrow { offset_x: 10.0, offset_y: 14.0 }) .with_point_labels(vec![ ("1", 40.0, "BRCA2"), ("2", 140.0, "TP53"), ("3", 250.0, "EGFR"), ]); }
Each significant peak is labeled with its gene name. The Arrow style draws a short leader line from the label to the point, keeping the label legible even when the peak is narrow.
The x value passed to with_point_labels must match the x coordinate assigned at data-load time. A tolerance of ±0.5 is used, so integer positions are always matched exactly.
Custom genome build
GenomeBuild::Custom accepts a Vec<(chrom_name, size_in_bp)> list in the order you want chromosomes displayed. Use this for non-human organisms or subsets of the human genome.
#![allow(unused)] fn main() { use kuva::plot::{ManhattanPlot, GenomeBuild}; use kuva::Palette; use kuva::render::plots::Plot; let build = GenomeBuild::Custom(vec![ ("chr1".to_string(), 120_000_000), ("chr2".to_string(), 95_000_000), ("chr3".to_string(), 80_000_000), ("chrX".to_string(), 55_000_000), ]); let mp = ManhattanPlot::new() .with_data_bp(data, build) .with_palette(Palette::tol_bright()) .with_label_top(6) .with_legend("GWAS thresholds"); }
Palette::tol_bright() cycles six colorblind-safe colors across the four chromosomes. Chromosome names are accepted with or without the "chr" prefix in both the build definition and the data.
Thresholds
| Method | Default | Description |
|---|---|---|
.with_genome_wide(f) | 7.301 | −log₁₀ threshold for the red dashed line (p = 5×10⁻⁸) |
.with_suggestive(f) | 5.0 | −log₁₀ threshold for the gray dashed line (p = 1×10⁻⁵) |
Pass −log₁₀(p) directly: for p = 1×10⁻⁶, use 6.0.
Colors
By default chromosomes alternate between two shades of blue:
| Method | Default | Description |
|---|---|---|
.with_color_a(s) | "steelblue" | Even-indexed chromosomes (0, 2, 4, …) |
.with_color_b(s) | "#5aadcb" | Odd-indexed chromosomes (1, 3, 5, …) |
.with_palette(p) | — | Full palette; overrides the two-color scheme |
All color methods accept any CSS color string. .with_palette() assigns colors in chromosome order, cycling with modulo wrapping.
Chromosome label overlap
At the default canvas width the small autosomes (17–22, X, Y) are narrow enough
that their chromosome labels can overlap. The with_x_label_overlap Layout
builder (and the --x-label-overlap CLI flag) controls how collisions are
handled:
#![allow(unused)] fn main() { use kuva::AxisLabelOverlap; use kuva::render::layout::Layout; use kuva::render::plots::Plot; use kuva::plot::manhattan::{GenomeBuild, ManhattanPlot}; let data: Vec<(String, f64, f64)> = vec![]; let mp = ManhattanPlot::new().with_data_bp(data, GenomeBuild::Hg38); let plots = vec![Plot::Manhattan(mp)]; // Thin — drop labels that would overprint their neighbour let layout = Layout::auto_from_plots(&plots) .with_x_label_overlap(AxisLabelOverlap::Thin); // Stagger — keep every label, alternate between two rows let layout = Layout::auto_from_plots(&plots) .with_x_label_overlap(AxisLabelOverlap::Stagger); }
| Strategy | Behaviour |
|---|---|
Allow (default) | Every chromosome label drawn; small chroms may overlap |
Thin | Overlapping labels skipped; spacing is always clean |
Stagger | All chromosomes labeled; colliding names alternate rows |
Stagger is generally preferable when you need all chromosome names visible —
it expands the bottom margin automatically to accommodate the second row.
CLI: kuva manhattan data.tsv --genome-build hg38 --x-label-overlap stagger
API reference
| Method | Description |
|---|---|
ManhattanPlot::new() | Create a plot with defaults |
.with_data(iter) | Load (chrom, pvalue) pairs; sequential integer x |
.with_data_bp(iter, build) | Load (chrom, bp, pvalue) triplets; true genomic x |
.with_data_x(iter) | Load (chrom, x, pvalue) triplets; pre-computed x |
.with_genome_wide(f) | Genome-wide threshold in −log₁₀ (default 7.301) |
.with_suggestive(f) | Suggestive threshold in −log₁₀ (default 5.0) |
.with_color_a(s) | Even-chromosome color (default "steelblue") |
.with_color_b(s) | Odd-chromosome color (default "#5aadcb") |
.with_palette(p) | Full palette, overrides alternating colors |
.with_point_size(f) | Circle radius in pixels (default 2.5) |
.with_label_top(n) | Label the n top hits above genome-wide threshold |
.with_label_style(s) | Nudge (default), Exact, or Arrow { offset_x, offset_y } |
.with_point_labels(iter) | Attach gene/SNP names to specific (chrom, x) positions |
.with_pvalue_floor(f) | Explicit p-value floor for −log₁₀ transform |
.with_legend(s) | Show genome-wide and suggestive legend entries |