Genome tracks with gene annotations¶
A browser-style locus view with a quantitative signal track stacked above a gene-annotation track. The lower panel combines transcript bodies, exons, labels, and strand cues so the annotations stay readable while zooming.
Code¶
"""Genome tracks with gene annotations.
A browser-style locus view with a quantitative signal track stacked above a
gene-annotation track. The lower panel combines transcript bodies, exons,
labels, and strand cues so the annotations stay readable while zooming.
"""
from __future__ import annotations
import genome_spy as gs
DOMAIN = [
{"chrom": "chr5", "pos": 177500000},
{"chrom": "chr5", "pos": 179500000},
]
# Show GC content, loading just the region being viewed.
gc_track = (
gs.Chart(
data=gs.lazy.bigwig("https://data.genomespy.app/genomes/hg38/hg38.gc5Base.bw")
)
.mark_rect(color="#6c8ebf", minWidth=0.5, minOpacity=1, tooltip=None)
.encode(
x=gs.Locus("chrom", "start"),
x2=gs.Locus("chrom", "end"),
y=gs.Y("score:Q")
.scale(domain=[0, 100], reverse=False)
.axis(title="GC (%)", grid=True, gridDash=[2, 2]),
)
.properties(
name="gc-content",
title=gs.title("GC content", orient="left"),
height=96,
)
.configure_view(stroke="lightgray")
)
# Draw exon blocks for the gene track below.
exons = (
gs.Chart()
.mark_rect(minOpacity=0.2, minWidth=0.5, tooltip=None)
.encode(x=gs.X("exonStart:L"), x2=gs.X2("exonEnd"))
.transform_project(fields=["_lane", "_start", "exons"])
.transform_flatten_compressed_exons(start="_start")
.properties(name="exons")
)
# Connect the exons with a thin line spanning each transcript.
bodies = (
gs.Chart()
.mark_rule(minLength=0.5, size=1, tooltip=None)
.encode(
x=gs.X("_start:L").axis(title="Genomic position"),
x2=gs.X2("_end"),
search=gs.Search("symbol"),
)
.properties(name="bodies", title="Gene annotations")
)
# Reveal the gene shapes as the reader zooms in.
transcripts = (
gs.layer(exons, bodies)
.properties(
name="transcripts",
opacity=gs.dynamic_opacity(unitsPerPixel=[100000, 40000], values=[0, 1]),
)
.encode(
color=gs.value("#909090"),
)
)
# Label genes and show more details on hover.
labels = (
gs.Chart()
.mark_text(size=11, yOffset=7, tooltip=gs.HandledTooltip(handler="refseqgene"))
.encode(x=gs.X("_centroid:L"), text=gs.Text("symbol:N"))
.properties(name="labels")
)
# Put a reading-direction arrow just beside each gene name.
arrows = (
gs.Chart()
.mark_point(yOffset=7, size=50, tooltip=None)
.encode(
x=gs.X("_centroid:L"),
dx=gs.Dx(
gs.expr(
(gs.datum._textWidth / 2 + 5)
* gs.expr.if_(gs.datum.strand == "-", -1, 1)
),
type="quantitative",
).scale(None),
color=gs.value("black"),
shape=gs.Shape("strand:N")
.scale(domain=["-", "+"], range=["triangle-left", "triangle-right"])
.legend(None),
)
.properties(
name="arrows",
opacity=gs.dynamic_opacity(unitsPerPixel=[100000, 40000], values=[0, 1]),
)
)
# Hide overlapping names, using the supplied scores to choose which to keep.
symbols = (
gs.layer(labels, arrows)
.properties(
name="symbols",
)
.transform_measure_text(
field="symbol",
as_="_textWidth",
fontSize=11,
)
.transform_filter_scored_labels(
lane="_lane",
score="score",
width="_textWidth",
pos="_centroid",
padding=5,
)
)
# Load one RefSeq table for the gene shapes and labels.
refseq_track = (
gs.layer(transcripts, symbols)
.properties(
name="refseq-track",
title=gs.title("RefSeq gene annotation", orient="left"),
height=gs.step(23),
data=gs.Data(
url="https://data.genomespy.app/genomes/hg38/refSeqGenes-hg38-release232.tsv.gz",
format=gs.data_format(
parse=gs.parse(
symbol="string",
chrom="string",
start="integer",
length="integer",
strand="string",
score="integer",
exons="string",
)
),
),
)
.encode(
y=gs.Y("_lane:O")
.scale(
type="index",
align=0,
paddingInner=0.4,
paddingOuter=0.2,
domain=[0, 3],
reverse=True,
zoom=False,
)
.axis(None),
)
# Find each transcript's start, end, and label position.
.transform_linearize_genomic_coordinate(
chrom="chrom",
pos="start",
as_="_start",
)
.transform_formula(
expr=gs.datum._start + gs.datum.length,
as_="_end",
)
.transform_formula(
expr=gs.datum._start + gs.datum.length / 2,
as_="_centroid",
)
# Put overlapping transcripts on separate rows, showing up to three rows.
.transform_collect(sort=gs.compare(["_start"]))
.transform_pileup(
start="_start",
end="_end",
as_="_lane",
preference="strand",
preferredOrder=["-", "+"],
)
.transform_filter(gs.datum._lane < 3)
)
# Put GC content above the genes so both tracks move together when zooming.
chart = (
gs.vconcat(gc_track, refseq_track, spacing=10)
.properties(
assembly="hg38",
title="GC content with RefSeq gene annotations",
description=(
"A genome browser view combining a GC-content signal track with "
"stacked RefSeq gene annotations."
),
scales=gs.scales(x=gs.Scale(domain=DOMAIN)),
)
.resolve_scale(y="independent")
.resolve_axis(y="independent")
)