Chromosomes and locus scales¶
A genomic position has two parts: a chromosome or other contig, and a position within that contig. GenomeSpy uses an assembly’s contig sizes and order to concatenate those coordinates into one continuous visual axis.
Use Locus() for this chromosome-aware encoding.
The resulting channel has the locus data type and automatically uses a locus
scale.
For a single unit view, keep a small domain close to its encoding:
gs.Locus("chrom", "pos").scale(domain=REGION)
When several views share a genomic scale, put the domain on their common parent:
tracks.properties(
scales=gs.scales(x=gs.Scale(domain=REGION)),
)
View-level domains are easier to reason about than domains buried in encodings deep in a view hierarchy, and they make the owner of a shared domain explicit.
Point features¶
Single nucleotide variants and other point features need one locus:
variants = [
{"chrom": "chr17", "pos": 43_044_295, "impact": "moderate"},
{"chrom": "chr17", "pos": 43_057_481, "impact": "high"},
{"chrom": "chr17", "pos": 43_070_977, "impact": "low"},
{"chrom": "chr17", "pos": 43_082_144, "impact": "high"},
]
point_chart = (
gs.Chart(variants)
.mark_point(filled=True, size=110)
.encode(
x=gs.Locus("chrom", "pos")
.scale(domain=BRCA1_DOMAIN)
.axis(title="Genomic position", chromGrid=True),
y=gs.Y("impact:N").title("Impact"),
color=gs.Color("impact:N").legend(None),
)
.properties(assembly="hg38", title="Point variants on chr17")
)
The root assembly="hg38" supplies chromosome sizes and order. The scale domain
focuses the initial view on a region of chromosome 17.
GenomeAxis options are available through .axis(...). In the example,
chromGrid=True enables chromosome-aware grid styling and the title identifies
the coordinate channel. The remaining options are listed in
genome axis for loci.
Genomic intervals¶
Features such as genes, exons, peaks, and copy-number segments cover a range.
Pair x with x2 to provide the two interval boundaries:
features = [
{"chrom": "chr17", "start": 43_044_000, "end": 43_050_000, "kind": "enhancer"},
{"chrom": "chr17", "start": 43_057_000, "end": 43_061_000, "kind": "exon"},
{"chrom": "chr17", "start": 43_068_000, "end": 43_075_000, "kind": "enhancer"},
]
interval_chart = (
gs.Chart(features)
.mark_rect()
.encode(
x=gs.Locus("chrom", "start").scale(domain=BRCA1_DOMAIN),
x2=gs.Locus("chrom", "end"),
y=gs.Y("kind:N").title("Feature kind"),
color=gs.Color("kind:N").legend(None),
)
.properties(assembly="hg38", title="Half-open genomic intervals")
)
The primary locus establishes the x scale and assembly. The secondary x2
channel supplies only the other endpoint and inherits that scale. The same
pattern works with rules, links, and arrows.
How chromosome positions become one axis¶
The assembly lists contigs in their preferred order and gives each contig’s
length. GenomeSpy places the first contig at the start of a continuous axis,
then places the next contig after it, and so on. Locus()
inserts the required coordinate-linearization step automatically.
Usually, keep chromosome and position in separate fields and let GenomeSpy
linearize them. A pre-linearized field can be encoded with a regular channel of
type L, but it is then your responsibility to ensure that it matches the
selected assembly. The GenomeSpy documentation covers both forms in
encoding genomic coordinates.
Coordinate counting and offsets¶
GenomeSpy’s internal genomic intervals are zero-based and half-open:
zero-based means the first base starts at position
0;half-open
[start, end)includesstartbut excludesend.
For example, [100, 103) covers three bases: 100, 101, and 102. This convention
makes interval length simply end - start.
Formats do not all use the same convention. VCF positions and GFF3 starts are
one-based. Pass offset=1 to subtract one from such a position during locus
encoding:
gff_features = [
{"seqid": "chr17", "start1": 43_044_001, "end1": 43_050_000, "name": "A"},
{"seqid": "chr17", "start1": 43_057_001, "end1": 43_061_000, "name": "B"},
]
offset_chart = (
gs.Chart(gff_features)
.mark_rect(color="#4c78a8")
.encode(
x=gs.Locus("seqid", "start1", offset=1).scale(domain=BRCA1_DOMAIN),
x2=gs.Locus("seqid", "end1"),
y=gs.Y("name:N").title("Feature"),
)
.properties(assembly="hg38", title="One-based inclusive input")
)
For a one-based inclusive interval such as GFF3 [1, 10], subtract one from the
start and leave the inclusive end unchanged. It then becomes the equivalent
zero-based half-open interval [0, 10). That is why the example sets
offset=1 only on start1.
For a one-based point such as a VCF POS, set offset=1 on its single locus
channel. Do not apply an offset merely because labels should begin at one; an
offset changes data coordinates, while axis label numbering is a presentation
choice. The same convention is described in
coordinate counting.
Built-in assemblies¶
GenomeSpy includes hg38, hg19, hg18, mm10, mm9, and dm6; the current
list is in
supported genomes.
Set one as the root default when all locus scales use it:
chart.properties(assembly="hg38")
An assembly can instead be set on a specific scale with
.scale(assembly="hg38"). Prefer the root property when several tracks share
the same coordinate system; it avoids repetition and keeps their relationship
clear.
Custom assemblies¶
A custom assembly needs contig names, sizes, and an intentional order. Define a
reusable assembly in the root genomes mapping and select it by name:
toy_genome = {
"contigs": [
{"name": "chrA", "size": 1_000},
{"name": "chrB", "size": 600},
{"name": "plasmid", "size": 250},
]
}
toy_features = [
{"chrom": "chrA", "start": 100, "end": 300, "label": "alpha"},
{"chrom": "chrB", "start": 50, "end": 220, "label": "beta"},
{"chrom": "plasmid", "start": 20, "end": 180, "label": "gamma"},
]
custom_assembly_chart = (
gs.Chart(toy_features)
.mark_rect()
.encode(
x=gs.Locus("chrom", "start"),
x2=gs.Locus("chrom", "end"),
y=gs.Y("label:N").axis(None),
color=gs.Color("label:N").legend(None),
)
.properties(
genomes={"toy": toy_genome},
assembly="toy",
title="A custom three-contig assembly",
)
)
The contig array determines the axis order: chrA, then chrB, then plasmid.
A custom definition may instead contain a url pointing to a two-column
chrom.sizes file. Define an assembly once under genomes when several scales
reuse it; a one-off inline definition can be placed in scale.assembly.
Contig names in the data must match the assembly. For example, 1 and chr1
are different names unless the data loader or preparation step normalizes them.
The accepted definition forms are described in
custom genomes.
Multiple assemblies¶
Most charts use one root assembly. Cross-species and coordinate-comparison views can assign a different assembly to each locus scale:
# Orthologous gene starts from the UCSC hg38 and mm10 RefSeq annotations.
homologs = [
{
"gene": "BRCA1",
"hsChrom": "chr17",
"hsPos": 43_044_295,
"mmChrom": "chr11",
"mmPos": 101_488_764,
},
{
"gene": "TP53",
"hsChrom": "chr17",
"hsPos": 7_668_421,
"mmChrom": "chr11",
"mmPos": 69_580_359,
},
{
"gene": "MYC",
"hsChrom": "chr8",
"hsPos": 127_736_231,
"mmChrom": "chr15",
"mmPos": 61_985_341,
},
]
multiple_assembly_chart = (
gs.Chart(homologs)
.mark_point(filled=True, size=120)
.encode(
# A chrom/pos domain is linearized without the scale's assembly, so it
# only resolves under a root assembly. These scales carry their own.
x=gs.Locus("hsChrom", "hsPos")
.scale(type="locus", assembly="hg38")
.axis(title="Human hg38", chromGrid=True),
y=gs.Locus("mmChrom", "mmPos")
.scale(type="locus", assembly="mm10")
.axis(title="Mouse mm10", chromGrid=True),
color=gs.Color("gene:N").legend(title="Gene"),
tooltip="gene:N",
)
.properties(title="Orthologous genes in two assemblies")
)
Here, x uses human hg38 coordinates and y uses mouse mm10 coordinates. A
root assembly is unnecessary because every locus scale identifies its own
assembly. Keep this scale-local form for genuinely different coordinate systems;
ordinary aligned genome-browser tracks should share one root assembly.
The cytoband ideogram and Manhattan plot examples put whole-genome locus scales to work.