Genome Track Viewer — lets-plot

A multi-track genome browser view that displays different types of genomic data aligned along a shared chromosomal coordinate axis. Multiple parallel horizontal tracks (gene annotations, read coverage, variants, regulatory elements) are stacked vertically, each showing a different data type at the same genomic locus. This visualization is essential for integrative genomics, enabling researchers to explore relationships between gene structure, expression, variation, and regulation in a single coordinated view.

Genome Track Viewer rendered with lets-plot

Python source (lets-plot)

""" anyplot.ai
genome-track-multi: Genome Track Viewer
Library: letsplot 4.10.1 | Python 3.13.13
Quality: 86/100 | Updated: 2026-06-02
"""

import os

import numpy as np
import pandas as pd
from lets_plot import *


LetsPlot.setup_html()

# Theme-adaptive chrome tokens (Imprint palette)
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint categorical palette
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"]
AMBER = "#DDCC77"

np.random.seed(42)

# Genomic region: chr7:27,200,000-27,280,000 (HOXA gene cluster)
chrom = "chr7"
region_start = 27200000
region_end = 27280000

# Track 1: Gene annotations
genes = pd.DataFrame(
    {
        "name": ["HOXA1", "HOXA2", "HOXA3", "HOXA4", "HOXA5"],
        "start": [27204000, 27220000, 27238000, 27252000, 27264000],
        "end": [27212000, 27229000, 27248000, 27260000, 27274000],
        "strand": ["+", "+", "+", "-", "+"],
    }
)

# Exons within each gene
exons = pd.DataFrame(
    {
        "gene": [
            "HOXA1",
            "HOXA1",
            "HOXA1",
            "HOXA2",
            "HOXA2",
            "HOXA3",
            "HOXA3",
            "HOXA3",
            "HOXA3",
            "HOXA4",
            "HOXA4",
            "HOXA4",
            "HOXA5",
            "HOXA5",
            "HOXA5",
        ],
        "start": [
            27204000,
            27206500,
            27210000,
            27220000,
            27225000,
            27238000,
            27241000,
            27244000,
            27246500,
            27252000,
            27255000,
            27258000,
            27264000,
            27268000,
            27271500,
        ],
        "end": [
            27205200,
            27207800,
            27212000,
            27221500,
            27229000,
            27239500,
            27242500,
            27245200,
            27248000,
            27253500,
            27256500,
            27260000,
            27265500,
            27269500,
            27274000,
        ],
    }
)

# Track 2: Coverage — simulated RNA-seq read depth
coverage_positions = np.arange(region_start, region_end, 200)
base_coverage = np.random.exponential(5, len(coverage_positions))
for _, exon in exons.iterrows():
    mask = (coverage_positions >= exon["start"]) & (coverage_positions <= exon["end"])
    base_coverage[mask] += np.random.exponential(40, mask.sum())
coverage_df = pd.DataFrame({"position": coverage_positions, "depth": np.clip(base_coverage, 0, 120)})

# Track 3: Variants (SNPs and indels)
variant_positions = np.sort(np.random.choice(range(region_start + 1000, region_end - 1000), size=18, replace=False))
variant_types = np.random.choice(["SNP", "SNP", "SNP", "Indel"], size=18)
variant_quality = np.random.uniform(20, 99, size=18)
variants_df = pd.DataFrame({"position": variant_positions, "type": variant_types, "quality": variant_quality})

# Track 4: Regulatory elements
regulatory = pd.DataFrame(
    {
        "start": [27201000, 27215000, 27233000, 27249000, 27262000, 27275000],
        "end": [27203000, 27218000, 27236000, 27251000, 27263500, 27278000],
        "element": ["Promoter", "Enhancer", "Promoter", "Enhancer", "Promoter", "Enhancer"],
    }
)

# Track layout
tracks = {
    "Genes": {"center": 4.2, "half_h": 0.42, "pad_bot": 0.25, "pad_top": 0.55},
    "Coverage": {"center": 2.7, "half_h": 0.65, "pad_bot": 0.25, "pad_top": 0.30},
    "Variants": {"center": 1.3, "half_h": 0.42, "pad_bot": 0.25, "pad_top": 0.30},
    "Regulatory": {"center": 0.0, "half_h": 0.42, "pad_bot": 0.25, "pad_top": 0.30},
}

scale = 1000.0
x_start = region_start / scale
x_end = region_end / scale

# Gene track data
t = tracks["Genes"]
gene_intron_df = pd.DataFrame(
    {"x": genes["start"] / scale, "xend": genes["end"] / scale, "y": t["center"], "yend": t["center"]}
)
exon_df = pd.DataFrame(
    {
        "xmin": exons["start"] / scale,
        "xmax": exons["end"] / scale,
        "ymin": t["center"] - t["half_h"],
        "ymax": t["center"] + t["half_h"],
        "gene": exons["gene"],
    }
)
exon_df["exon_size"] = ((exons["end"] - exons["start"]) / 1000).round(1).astype(str) + " kb"
gene_label_df = pd.DataFrame(
    {"x": (genes["start"] + genes["end"]) / 2 / scale, "y": t["center"] + t["half_h"] + 0.28, "label": genes["name"]}
)
strand_arrows = []
for _, g in genes.iterrows():
    mid = (g["start"] + g["end"]) / 2 / scale
    arrow_char = "▶" if g["strand"] == "+" else "◀"
    strand_arrows.append({"x": mid, "y": t["center"] - t["half_h"] - 0.24, "label": arrow_char})
strand_df = pd.DataFrame(strand_arrows)

# Coverage track data
t = tracks["Coverage"]
cov_plot_df = coverage_df.copy()
cov_plot_df["x"] = cov_plot_df["position"] / scale
max_depth = cov_plot_df["depth"].max()
cov_plot_df["y"] = t["center"] - t["half_h"] + (cov_plot_df["depth"] / max_depth) * (2 * t["half_h"])
cov_plot_df["ybase"] = t["center"] - t["half_h"]
cov_plot_df["depth_label"] = cov_plot_df["depth"].round(1).astype(str) + "x"
cov_plot_df["pos_label"] = (cov_plot_df["position"] / 1000).round(1).astype(str) + " kb"

# Variant track data
t = tracks["Variants"]
var_plot_df = variants_df.copy()
var_plot_df["x"] = var_plot_df["position"] / scale
var_plot_df["y_base"] = t["center"]
min_stem = 0.5  # raised from 0.3 — improves low-quality variant visibility
max_stem = 2 * t["half_h"] + 0.15
var_plot_df["y_top"] = t["center"] + min_stem * max_stem + (var_plot_df["quality"] / 100) * (1 - min_stem) * max_stem
var_plot_df["qual_label"] = "QUAL: " + var_plot_df["quality"].round(1).astype(str)
var_plot_df["pos_label"] = (var_plot_df["position"] / 1000).round(1).astype(str) + " kb"

# Regulatory track data
t = tracks["Regulatory"]
reg_plot_df = pd.DataFrame(
    {
        "xmin": regulatory["start"] / scale,
        "xmax": regulatory["end"] / scale,
        "ymin": t["center"] - t["half_h"],
        "ymax": t["center"] + t["half_h"],
        "element": regulatory["element"],
        "size_label": ((regulatory["end"] - regulatory["start"]) / 1000).round(1).astype(str) + " kb",
    }
)

# Track background shading (alternating light/dark)
bg_rows = []
for name, t in tracks.items():
    bg_rows.append(
        {
            "xmin": x_start,
            "xmax": x_end,
            "ymin": t["center"] - t["half_h"] - t["pad_bot"],
            "ymax": t["center"] + t["half_h"] + t["pad_top"],
            "track": name,
        }
    )
track_bg = pd.DataFrame(bg_rows)

# Divider lines
track_order = ["Regulatory", "Variants", "Coverage", "Genes"]
divider_positions = []
for i in range(len(track_order) - 1):
    lo = tracks[track_order[i]]
    hi = tracks[track_order[i + 1]]
    y_mid = (lo["center"] + lo["half_h"] + lo["pad_top"] + hi["center"] - hi["half_h"] - hi["pad_bot"]) / 2
    divider_positions.append(y_mid)
divider_df = pd.DataFrame({"x": [x_start] * 3, "xend": [x_end] * 3, "y": divider_positions, "yend": divider_positions})

# Track labels shifted right to clear leftmost data elements
track_labels_df = pd.DataFrame(
    {"x": [x_start + 3.5] * 4, "y": [tracks[n]["center"] for n in track_order], "label": track_order}
)

# Highlight region: variant-dense area near HOXA3
highlight_df = pd.DataFrame(
    {
        "xmin": [27241.0],
        "xmax": [27243.0],
        "ymin": [tracks["Regulatory"]["center"] - tracks["Regulatory"]["half_h"] - tracks["Regulatory"]["pad_bot"]],
        "ymax": [tracks["Genes"]["center"] + tracks["Genes"]["half_h"] + tracks["Genes"]["pad_top"]],
    }
)

# Peak coverage annotation
peak_idx = cov_plot_df["depth"].idxmax()
peak_x = cov_plot_df.loc[peak_idx, "x"]
peak_depth = cov_plot_df.loc[peak_idx, "depth"]
t = tracks["Coverage"]
peak_annotation_df = pd.DataFrame(
    {"x": [peak_x + 2.0], "y": [t["center"] + t["half_h"] - 0.05], "label": [f"Peak: {peak_depth:.0f}x"]}
)

# Colors — Imprint palette assignments (fixes green/red colorblind pair)
exon_color = IMPRINT[2]  # #4467A3 blue — gene exons (structural)
cov_fill = IMPRINT[0]  # #009E73 green — coverage (first categorical series)
snp_color = IMPRINT[1]  # #C475FD lavender — SNP
indel_color = IMPRINT[4]  # #AE3030 red — Indel (semantic bad/error)
promoter_color = IMPRINT[3]  # #BD8233 ochre — Promoter (replaces green, fixes colorblind issue)
enhancer_color = IMPRINT[5]  # #2ABCCD cyan — Enhancer

# Highlight colors: amber (theme-independent warning/focus)
highlight_fill = "#FFF8E1" if THEME == "light" else "#2A2000"
highlight_border = AMBER

tick_positions = list(range(int(x_start), int(x_end) + 1, 20))
tick_labels = [f"{v} kb" for v in tick_positions]

# Title: genome-track-multi · python · letsplot · anyplot.ai  (51 chars < 67 baseline, no scaling needed)
title_str = "genome-track-multi · python · letsplot · anyplot.ai"

plot = (
    ggplot()
    # Highlight region
    + geom_rect(
        aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"),
        data=highlight_df,
        fill=highlight_fill,
        color=highlight_border,
        size=0.5,
        alpha=0.4,
    )
    # Track background shading
    + geom_rect(
        aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"),
        data=track_bg,
        fill=ELEVATED_BG,
        color=INK_MUTED,
        size=0.2,
        alpha=0.5,
    )
    # Divider lines
    + geom_segment(
        aes(x="x", xend="xend", y="y", yend="yend"), data=divider_df, color=INK_SOFT, size=0.3, linetype="dashed"
    )
    # Gene track: intron lines
    + geom_segment(aes(x="x", xend="xend", y="y", yend="yend"), data=gene_intron_df, color=exon_color, size=1.0)
    # Gene track: exon rectangles
    + geom_rect(
        aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"),
        data=exon_df,
        fill=exon_color,
        color=INK,
        size=0.5,
        alpha=0.85,
        tooltips=layer_tooltips().line("Gene: @gene").line("Size: @exon_size"),
    )
    # Gene labels (italic, above exons)
    + geom_text(aes(x="x", y="y", label="label"), data=gene_label_df, size=4, color=INK, fontface="italic")
    # Strand direction arrows — size raised to 3.5 mm for legibility
    + geom_text(aes(x="x", y="y", label="label"), data=strand_df, size=3.5, color=INK_SOFT)
    # Coverage: filled area
    + geom_ribbon(
        aes(x="x", ymin="ybase", ymax="y"),
        data=cov_plot_df,
        fill=cov_fill,
        color=cov_fill,
        alpha=0.55,
        size=0.4,
        tooltips=layer_tooltips().line("Position: @pos_label").line("Depth: @depth_label"),
    )
    # Coverage peak annotation
    + geom_text(aes(x="x", y="y", label="label"), data=peak_annotation_df, size=3.5, color=INK, fontface="bold")
    # Variant lollipop stems
    + geom_segment(aes(x="x", xend="x", y="y_base", yend="y_top", color="type"), data=var_plot_df, size=1.0)
    # Variant lollipop heads
    + geom_point(
        aes(x="x", y="y_top", color="type"),
        data=var_plot_df,
        size=5,
        stroke=0.8,
        tooltips=layer_tooltips().line("@type").line("@qual_label").line("Position: @pos_label"),
    )
    # Regulatory elements
    + geom_rect(
        aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax", fill="element"),
        data=reg_plot_df,
        alpha=0.85,
        size=0.4,
        color=PAGE_BG,
        tooltips=layer_tooltips().line("@element").line("Size: @size_label"),
    )
    # Track labels at center y — avoids overlap with gene names at top of Genes track
    + geom_text(aes(x="x", y="y", label="label"), data=track_labels_df, size=4.5, color=INK, fontface="bold", hjust=0)
    # Scales
    + scale_color_manual(values={"SNP": snp_color, "Indel": indel_color}, name="Variant Type")
    + scale_fill_manual(values={"Promoter": promoter_color, "Enhancer": enhancer_color}, name="Regulatory")
    + scale_x_continuous(
        name=f"Genomic Position ({chrom})", breaks=tick_positions, labels=tick_labels, expand=[0.01, 0.01]
    )
    + scale_y_continuous(expand=[0.04, 0.04])
    + labs(
        title=title_str,
        subtitle="HOXA Gene Cluster — chr7:27,200–27,280 kb  |  Highlight: variant-rich region near HOXA3",
    )
    + coord_cartesian(xlim=[x_start - 1, x_end + 1])
    + theme_minimal()
    + theme(
        plot_title=element_text(size=16, face="bold", color=INK),
        plot_subtitle=element_text(size=11, color=INK_SOFT),
        axis_title_x=element_text(size=12, color=INK),
        axis_title_y=element_blank(),
        axis_text_x=element_text(size=10, color=INK_SOFT, angle=0),
        axis_text_y=element_blank(),
        axis_ticks_y=element_blank(),
        panel_grid_major_x=element_line(color=INK_SOFT, size=0.15),
        panel_grid_major_y=element_blank(),
        panel_grid_minor=element_blank(),
        legend_title=element_text(size=10, face="bold", color=INK),
        legend_text=element_text(size=10, color=INK_SOFT),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
        legend_position="bottom",
        legend_box="horizontal",
        legend_direction="horizontal",
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        plot_margin=[10, 10, 5, 10],
    )
    + ggsize(800, 450)
)

# Save — scale=4 → 3200×1800 px
ggsave(plot, f"plot-{THEME}.png", path=".", scale=4)
ggsave(plot, f"plot-{THEME}.html", path=".")

Part of Genome Track Viewer on anyplot.ai.

Other implementations