Synteny from gene symbols (eight primates)
We look at one human locus across seven other primates at once. 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, laid out in that genome's own coordinates with its own gene models annotated on it. The join reaches exactly as far as the naming does, and a gene family whose copies carry placeholder names is where it stops.
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. Only the annotation
and the sequence report are downloaded, a few hundred megabytes for the eight.
Human is GRCh38 rather than T2T-CHM13 so the window's coordinates are the ones
the rest of the ecosystem quotes.
- 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 joined on gene names
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. Genomes annotated by one pipeline fill that
table a third way. 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
link joins one gene to one gene, so a symbol several genes in one genome carry
gets a row per copy, which the
OrthoFinder page
draws as a ribbon per copy over maize's whole-genome duplication. Little of that
reaches these eight: RefSeq gives a duplicated primate gene either its own
lettered symbol, AMY1A against AMY1B, or a LOC placeholder. The
pseudoautosomal genes are the exception: annotated on both X and Y, each Y copy
gets a separate row. 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, one FeatureTrack per genome. 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. The
color is a hash of the symbol, so it identifies a gene and carries nothing else.
A key naming the symbols appears in the top right once the window holds few
enough genes to list, and stays out of the way at the windows below:
{
"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.gz" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed.gz" },
{ "uri": "chimp.bed.gz" },
{ "uri": "bonobo.bed.gz" },
{ "uri": "gorilla.bed.gz" },
{ "uri": "sumatran.bed.gz" },
{ "uri": "bornean.bed.gz" },
{ "uri": "siamang.bed.gz" },
{ "uri": "macaque.bed.gz" }
]
},
"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.gz" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed.gz" },
{ "uri": "chimp.bed.gz" },
{ "uri": "bonobo.bed.gz" },
{ "uri": "gorilla.bed.gz" },
{ "uri": "sumatran.bed.gz" },
{ "uri": "bornean.bed.gz" },
{ "uri": "siamang.bed.gz" },
{ "uri": "macaque.bed.gz" }
]
},
"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.gz" },
"blockAssemblies": [
"human",
"chimp",
"bonobo",
"gorilla",
"sumatran",
"bornean",
"siamang",
"macaque"
],
"bedLocations": [
{ "uri": "human.bed.gz" },
{ "uri": "chimp.bed.gz" },
{ "uri": "bonobo.bed.gz" },
{ "uri": "gorilla.bed.gz" },
{ "uri": "sumatran.bed.gz" },
{ "uri": "bornean.bed.gz" },
{ "uri": "siamang.bed.gz" },
{ "uri": "macaque.bed.gz" }
]
},
"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 colors each ribbon by
the strand of the pair it joins, the two lanes' orientations against the human
axis multiplied out, rather than by whether the ribbon is drawn crossed. A lane
reading the block backwards is drawn mirrored, so its ribbons come out straight
while the strand color still marks every one of them as an inversion.
The stack sorts densest-first, so the genome sharing the most of the window's
genes sits directly under the anchor: a ribbon joins adjacent lanes only, and a
sparse lane in the middle would cut every chain running through it. That makes
lane order a property of the window rather than of the phylogeny, and it differs
between the pictures below. Move up and Move down on a lane's header
menu pin an order, Lanes → Reset lane order on the track menu gives it back,
and a session or a config authors the same thing as rowOrder.
{
"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 four megabases the same track reads as a synteny painting, and color separates the two things it draws. A bundle painted as reversed is a block that lane reads backwards from the lane above it, whether the mirroring left it drawn straight or crossed: one block runs down the middle of the frame with same-orientation flanks on either side of it, and one pair of lanes crosses near the right-hand edge. The headers give the other fact: every lane sits at its own offset and its own scale, and a lane whose header names a multiple is holding the same genes over more sequence.
A chromosome fusion
Human chromosome 2 is two ape chromosomes joined end to end. The same track in a linear synteny view shows it, with human chr2 on one row and the two chimpanzee chromosomes that carry its halves on the other. The palette button's Target paints each ribbon by the chimpanzee chromosome it lands on.
In the lanes, a window across the fusion point has orthologs on both chimpanzee chromosomes, and a lane follows one contig at a time. Each ape lane picks the one holding more of the window's genes and names the other in its header, and Show ⟨contig⟩ in this lane on the header menu swaps the lane onto it.
Placeholder names
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 most of them have no row in the table. Every lane still draws the
gene copies annotated in that genome, and the ribbons stop where the naming
does. A locus like this needs a real homology call, such as the one 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 from gene symbols (44 E. coli genomes)
- 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.