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();
}
Manhattan plot — sequential mode, all chromosomes

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");
}
Manhattan plot — GRCh38 base-pair coordinates with top-hit labels

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:

VariantAssemblyChromosomes
GenomeBuild::Hg19GRCh37 / hg191–22, X, Y, MT
GenomeBuild::Hg38GRCh38 / hg381–22, X, Y, MT
GenomeBuild::T2TT2T-CHM13 v2.01–22, X, Y, MT
GenomeBuild::Custom(…)User-definedAny

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"),
    ]);
}
Manhattan plot with gene-name labels (Arrow style)

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");
}
Manhattan plot — custom four-chromosome genome with tol_bright palette

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

MethodDefaultDescription
.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:

MethodDefaultDescription
.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.


API reference

MethodDescription
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