Synteny from gene symbols (eight primates)
TL;DR: we look at one human locus across seven other primates at once, without aligning any of the genomes. NCBI gives an orthologous gene the same symbol in every species it annotates, so an ortholog table is a join on the gene name, built from eight GFF3 files in seconds. Each primate then becomes a lane under the human view, in its own coordinates, carrying its own gene models. A gene family whose copies got placeholder names is what the join cannot see, and the page ends there.
Prerequisites
- The
NCBI datasets CLI
(
datasetsanddataformat) - htslib (
bgzip,tabix) python3- A running JBrowse instance (the web quickstart or the desktop quickstart)
Where the data comes from
Eight RefSeq assemblies, each fetched by accession with the datasets CLI: the
current human reference, the six NHGRI telomere-to-telomere ape assemblies (Yoo
et al. 2025) and the telomere-to-telomere rhesus macaque.
- human, GRCh38.p14: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/001/405/GCF_000001405.40_GRCh38.p14/
- chimpanzee: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/028/858/775/GCF_028858775.2_NHGRI_mPanTro3-v2.1_pri/
- bonobo: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/029/289/425/GCF_029289425.2_NHGRI_mPanPan1-v2.1_pri/
- gorilla: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/029/281/585/GCF_029281585.2_NHGRI_mGorGor1-v2.1_pri/
- Sumatran orangutan: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/028/885/655/GCF_028885655.2_NHGRI_mPonAbe1-v2.1_pri/
- Bornean orangutan: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/028/885/625/GCF_028885625.2_NHGRI_mPonPyg2-v2.1_pri/
- siamang: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/028/878/055/GCF_028878055.3_NHGRI_mSymSyn1-v2.1_pri/
- rhesus macaque, T2T-MMU8v2.0: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/049/350/105/GCF_049350105.2_T2T-MMU8v2.0/
- the finished table, BEDs and config, rehosted so the lanes load without rerunning the pipeline: https://jbrowse.org/demos/primate_orthologs/config.json
An ortholog table without an aligner
The grape, peach and cacao
page builds its ortholog table by aligning coding sequence, and the
OrthoFinder page by clustering proteins.
Both produce the same .blocks shape: one row per orthologous group, one column
per genome, a gene id in each cell. For genomes annotated by one pipeline there
is a third way to fill that table. NCBI's eukaryotic annotation pipeline names a
gene after its ortholog, so human TP53 is chimpanzee TP53 and gorilla
TP53, and the table is a join on the Name attribute of each GFF3.
The join needs only the annotations, so the download is a GFF3 and a sequence report per genome:
# one RefSeq accession per line; the first is the genome the rows are anchored on
datasets download genome accession --inputfile accessions.txt \
--include gff3,seq-report --filename genomes.zip
unzip genomes.zip
symbols_to_blocks.py reads each GFF3 once, writes a BED of its protein-coding
genes and the table, and prints the column order it used:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/symbols_to_blocks.py
# --anchor names the genome whose genes are the rows; every other column is
# that gene's ortholog, or a dot
python3 symbols_to_blocks.py --anchor human -o primates.blocks \
human=human.gff.gz chimp=chimp.gff.gz bonobo=bonobo.gff.gz gorilla=gorilla.gff.gz \
sumatran=sumatran.gff.gz bornean=bornean.gff.gz siamang=siamang.gff.gz macaque=macaque.gff.gz
Symbols are compared case-folded, so a mouse Atp5f1a would meet the human
ATP5F1A, and a gene whose name is an NCBI LOC placeholder joins nothing. A
symbol appearing twice in one genome is a paralog and the first copy in file
order takes the cell. The helper prints how much of each column it filled; for
these eight the lanes come back nearly full, because the annotations share one
naming pipeline.
Setting up the assemblies
The lanes never read sequence, so each assembly is the chromosome lengths from
the sequence report, with the same report as its alias table so chr17 resolves
to NC_000017.11:
# the four columns NcbiSequenceReportAliasAdapter reads
dataformat tsv genome-seq --package genomes.zip \
--inputfile GCF_000001405.40/sequence_report.jsonl \
--fields genbank-seq-acc,refseq-seq-acc,sequence-name,ucsc-style-name \
> human.sequence_report.tsv
{
"name": "human",
"displayName": "Human (GRCh38.p14)",
"sequence": {
"type": "ReferenceSequenceTrack",
"trackId": "human-ReferenceSequenceTrack",
"adapter": {
"type": "ChromSizesAdapter",
"chromSizesLocation": { "uri": "human.chrom.sizes" }
}
},
"refNameAliases": {
"adapter": {
"type": "NcbiSequenceReportAliasAdapter",
"location": { "uri": "human.sequence_report.tsv" }
}
}
}
Each GFF3, filtered to the assembled chromosomes, sorted, bgzipped and tabix-indexed as in the web quickstart, is that genome's gene track. A lane finds its gene models through the session, so the track only has to exist under the lane's assembly name.
The ortholog track
One SyntenyTrack names all eight assemblies. blockAssemblies and
bedLocations are positional against the table's columns, in the order the
helper printed. The display colors a gene by its symbol, so a conserved gene is
one color down the whole stack and a lane missing it breaks the column:
{
"type": "SyntenyTrack",
"trackId": "primate_orthologs",
"name": "Primate orthologs by gene symbol (8 genomes, RefSeq)",
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"mcscanBlocksLocation": { "uri": "primates.blocks" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed" },
{ "uri": "chimp.bed" },
{ "uri": "bonobo.bed" },
{ "uri": "gorilla.bed" },
{ "uri": "sumatran.bed" },
{ "uri": "bornean.bed" },
{ "uri": "siamang.bed" },
{ "uri": "macaque.bed" }
],
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"displayId": "primate_orthologs-MultiWaySyntenyDisplay",
"color": "jexl:feature.name ? randomColor(feature.name) : '#b0b0b0'"
}
]
}
jbrowse add-track-json '{
"type": "SyntenyTrack",
"trackId": "primate_orthologs",
"name": "Primate orthologs by gene symbol (8 genomes, RefSeq)",
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"mcscanBlocksLocation": { "uri": "primates.blocks" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed" },
{ "uri": "chimp.bed" },
{ "uri": "bonobo.bed" },
{ "uri": "gorilla.bed" },
{ "uri": "sumatran.bed" },
{ "uri": "bornean.bed" },
{ "uri": "siamang.bed" },
{ "uri": "macaque.bed" }
],
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"displayId": "primate_orthologs-MultiWaySyntenyDisplay",
"color": "jexl:feature.name ? randomColor(feature.name) : '\''#b0b0b0'\''"
}
]
}'
In JBrowse Desktop, or in any running JBrowse Web session, open a view on this track’s assembly, then File → Open track..., choose Add track from pasted JSON, and paste:
{
"type": "SyntenyTrack",
"trackId": "primate_orthologs",
"name": "Primate orthologs by gene symbol (8 genomes, RefSeq)",
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"mcscanBlocksLocation": { "uri": "primates.blocks" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed" },
{ "uri": "chimp.bed" },
{ "uri": "bonobo.bed" },
{ "uri": "gorilla.bed" },
{ "uri": "sumatran.bed" },
{ "uri": "bornean.bed" },
{ "uri": "siamang.bed" },
{ "uri": "macaque.bed" }
],
"assemblyNames": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"displayId": "primate_orthologs-MultiWaySyntenyDisplay",
"color": "jexl:feature.name ? randomColor(feature.name) : '#b0b0b0'"
}
]
}
One locus, eight genomes
Opened in a linear genome view on human, the track draws a lane per genome under
the human axis, each fitted to wherever that genome keeps the window's genes. A
lane's header names its chromosome, the span it shows and [rev] where it reads
the other way, and the ribbons between adjacent lanes join each gene to its
ortholog. Color ribbons by → Strand on the track menu paints a ribbon that
crosses, which is a gene running the other way relative to the lane above.
{
"defaultSession": {
"name": "TP53 neighbourhood across eight primates",
"views": [
{
"type": "LinearGenomeView",
"assembly": "human",
"loc": "chr17:7,400,000-7,700,000",
"tracks": [
{
"trackId": "primate_orthologs",
"type": "MultiWaySyntenyDisplay",
"ribbonColorBy": "strand",
"height": 620
}
]
}
]
}
}
jbrowse set-default-session --session - << 'EOF'
{
"name": "TP53 neighbourhood across eight primates",
"views": [
{
"type": "LinearGenomeView",
"assembly": "human",
"loc": "chr17:7,400,000-7,700,000",
"tracks": [
{
"trackId": "primate_orthologs",
"type": "MultiWaySyntenyDisplay",
"ribbonColorBy": "strand",
"height": 620
}
]
}
]
}
EOF
Zoomed out to ten megabases the same track reads as a synteny painting. The ribbons bundle where gene order is shared and cross where a block is inverted in one lineage, and a lane whose header carries a multiple is holding the same genes over more sequence.
A window across a chromosome fusion
Human chromosome 2 is two ape chromosomes joined end to end. A window over the fusion point has orthologs on both of them, and a lane can follow one contig at a time, so each ape lane picks the one holding more of the window's genes. A lane whose other chromosome still holds a fair share of the window names it in its header, and Show ⟨contig⟩ in this lane on the header menu swaps the lane onto it. The genes on the other side of the fusion keep their models in every lane and have no ribbon.
What a symbol join cannot see
The join is exactly as good as the naming. The salivary amylase cluster on human
chr1 is a run of near-identical copies whose human names are lettered (AMY1A,
AMY1B, AMY1C), while the other primates' copies were left as placeholder
LOC ids, so the table holds no row for them. Every lane still draws its own
copies, from its own annotation, and the ribbons stop at the cluster's edge.
For a locus like this the table wants a real homology call, which is what the OrthoFinder page builds, or an alignment.
Reproduce it end to end
The script fetches the eight annotations, builds the table and writes the config; see Prerequisites.
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_primate_orthologs.sh
bash build_primate_orthologs.sh
See also
- Synteny from an ortholog table (grape, peach, cacao)
- Synteny visualization (OrthoFinder orthogroups)
- Synteny visualization (all-vs-all minimap2)
References
- Yoo D, Rhie A, et al. Complete sequencing of ape genomes. Nature (2025). https://doi.org/10.1038/s41586-025-08816-3
- O'Leary NA, et al. Reference sequence (RefSeq) database at NCBI: current status, taxonomic expansion, and functional annotation. Nucleic Acids Res (2016). https://doi.org/10.1093/nar/gkv1189
- NCBI Datasets: https://www.ncbi.nlm.nih.gov/datasets/
Feedback on this tutorial is welcome: contact us.