Synteny Plot

A synteny plot visualises conserved genomic regions between two or more sequences. Each sequence is drawn as a horizontal bar; collinear blocks are drawn as ribbons connecting the matching regions. Forward blocks use parallel-sided ribbons; inverted (reverse-complement) blocks draw crossed (bowtie) ribbons. The plot is well suited for comparing chromosomes, assembled genomes, or any ordered sequence data.

Import path: kuva::plot::synteny::SyntenyPlot


Basic usage

Add sequences with .with_sequences() as (label, length) pairs, then connect collinear regions with .with_block(seq1, start1, end1, seq2, start2, end2). Sequences are referred to by their 0-based index in the order they were added.

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

let plot = SyntenyPlot::new()
    .with_sequences([
        ("Human chr1", 248_956_422.0),
        ("Mouse chr1", 195_471_971.0),
    ])
    .with_block(0,   0.0,        50_000_000.0,  1,   0.0,        45_000_000.0)
    .with_block(0,  60_000_000.0, 120_000_000.0, 1,  55_000_000.0, 100_000_000.0)
    .with_block(0, 130_000_000.0, 200_000_000.0, 1, 110_000_000.0, 170_000_000.0);

let plots = vec![Plot::Synteny(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Human chr1 vs Mouse chr1");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("synteny.svg", svg).unwrap();
}
Pairwise synteny — Human vs Mouse chr1

By default each bar fills the full plot width regardless of sequence length (per-sequence scale). Ribbons are drawn before bars so the bars overlay them cleanly. Labels are right-padded to the left of each bar.


Inversions

.with_inv_block() marks a region as reverse-complement: the ribbon connects the right edge of the source interval to the left edge of the target (and vice versa), producing a characteristic crossed or bowtie shape.

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

let plot = SyntenyPlot::new()
    .with_sequences([("Seq A", 1_000_000.0), ("Seq B", 1_000_000.0)])
    .with_block(    0,       0.0, 200_000.0, 1,       0.0, 200_000.0)
    .with_inv_block(0, 250_000.0, 500_000.0, 1, 250_000.0, 500_000.0)  // inverted
    .with_block(    0, 600_000.0, 900_000.0, 1, 600_000.0, 900_000.0);

let plots = vec![Plot::Synteny(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Forward and Inverted Blocks");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Synteny plot with a crossed inversion ribbon

Multiple sequences

Add more than two sequences to show a stack of pairwise comparisons. A block can connect any two sequence indices — typically adjacent pairs for a multi-genome alignment view.

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

let plot = SyntenyPlot::new()
    .with_sequences([
        ("Genome A", 500_000.0),
        ("Genome B", 480_000.0),
        ("Genome C", 450_000.0),
    ])
    // blocks between sequences 0 and 1
    .with_block(    0,       0.0, 100_000.0, 1,       0.0,  95_000.0)
    .with_block(    0, 150_000.0, 300_000.0, 1, 140_000.0, 280_000.0)
    // blocks between sequences 1 and 2
    .with_block(    1,       0.0, 100_000.0, 2,   5_000.0, 105_000.0)
    .with_inv_block(1, 200_000.0, 350_000.0, 2, 190_000.0, 340_000.0);

let plots = vec![Plot::Synteny(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Three-way Synteny");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Synteny plot with three sequences

Blocks do not need to connect adjacent sequences — a block between sequences 0 and 2 will span the full height of the diagram.


Custom colors & legend

Set bar colors with .with_sequence_colors(). Override the ribbon color of individual blocks with .with_colored_block() or .with_colored_inv_block(). Call .with_legend("") to enable the legend; each sequence gets an entry labeled with its name.

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

let plot = SyntenyPlot::new()
    .with_sequences([("Seq 1", 500_000.0), ("Seq 2", 500_000.0)])
    .with_sequence_colors(["#4393c3", "#d6604d"])          // bar fill colors
    .with_colored_block(    0,       0.0, 150_000.0,
                            1,       0.0, 150_000.0, "#2ca02c")
    .with_colored_block(    0, 200_000.0, 350_000.0,
                            1, 200_000.0, 340_000.0, "#9467bd")
    .with_colored_inv_block(0, 380_000.0, 480_000.0,
                             1, 370_000.0, 470_000.0, "#ff7f0e")
    .with_legend("Blocks");

let plots = vec![Plot::Synteny(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Custom Colors & Legend");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Synteny with custom bar and ribbon colors

Without explicit block colors, ribbons inherit the source sequence bar color.


Shared scale

By default each bar fills the full plot width independently (per-sequence scale), which maximises detail for each sequence but hides length differences between them. Call .with_shared_scale() to use a common ruler: each bar's width is proportional to sequence_length / max_length, so relative sizes are accurately represented.

#![allow(unused)]
fn main() {
use kuva::plot::synteny::SyntenyPlot;
let plot = SyntenyPlot::new()
    .with_sequences([("Long", 1_000_000.0), ("Short", 400_000.0)])
    .with_shared_scale()                                   // shorter bar is 40% width
    .with_block(0,       0.0, 300_000.0, 1,      0.0, 300_000.0)
    .with_block(0, 350_000.0, 700_000.0, 1, 50_000.0, 380_000.0);
}

Bulk block loading

.with_blocks() accepts an iterator of pre-built SyntenyBlock structs, which is convenient when blocks come from a parsed alignment file:

#![allow(unused)]
fn main() {
use kuva::plot::synteny::{SyntenyPlot, SyntenyBlock, Strand};

let blocks: Vec<SyntenyBlock> = vec![
    SyntenyBlock { seq1: 0, start1:       0.0, end1: 100_000.0,
                   seq2: 1, start2:       0.0, end2:  95_000.0,
                   strand: Strand::Forward, color: None },
    SyntenyBlock { seq1: 0, start1: 150_000.0, end1: 300_000.0,
                   seq2: 1, start2: 140_000.0, end2: 280_000.0,
                   strand: Strand::Reverse, color: Some("#e41a1c".into()) },
];

let plot = SyntenyPlot::new()
    .with_sequences([("Ref", 500_000.0), ("Query", 480_000.0)])
    .with_blocks(blocks);
}

API reference

MethodDescription
SyntenyPlot::new()Create a synteny plot with defaults
.with_sequences(iter)Add sequences from (label, length) pairs
.with_sequence_colors(iter)Override bar fill colors (parallel to sequences)
.with_block(s1, start1, end1, s2, start2, end2)Add a forward collinear block
.with_inv_block(s1, start1, end1, s2, start2, end2)Add an inverted (crossed ribbon) block
.with_colored_block(s1, start1, end1, s2, start2, end2, color)Forward block with explicit ribbon color
.with_colored_inv_block(s1, start1, end1, s2, start2, end2, color)Inverted block with explicit ribbon color
.with_blocks(iter)Batch-add pre-built SyntenyBlock structs
.with_bar_height(px)Sequence bar height in pixels (default 18.0)
.with_opacity(f)Ribbon fill opacity 0.01.0 (default 0.65)
.with_shared_scale()Use a common ruler — bar width proportional to sequence length
.with_legend("")Enable the legend; one entry per sequence, labeled with the sequence name

Strand variants

VariantRibbon shape
ForwardParallel-sided trapezoid
ReverseCrossed / bowtie