Synteny from gene symbols (primates and E. coli)
The tutorials target the JBrowse v5 beta, and the v4.3.0 release
on the download page lacks some of what they show. To install the
beta of JBrowse Web, run npm install -g @jbrowse/cli@next, then
jbrowse create jbrowse2 --branch v5.0.0-beta.20. Desktop beta builds for Linux and
macOS are on the releases page;
the Windows build waits on code signing.
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 over eight GFF3 files. Bacterial annotations name fewer genes and rename more of them, so the page closes on E. coli to see what the join does with them. We:
- build the join, and set up each lane's assembly from its hub
- load the table as a multi-genome ortholog track, each primate a lane under the human view in the coordinates of that primate's genome
- read the TP53 neighbourhood and a 17q window across the lanes
- read human chromosome 2 against the ape chromosomes it fused from
- rebuild the join over E. coli annotations, with lanes built from chromosome lengths, and read the O-antigen cluster
Prerequisites
- The NCBI datasets CLI
- htslib (
bgzip,tabix), for the E. coli build python3- A running JBrowse instance (the web quickstart or the desktop quickstart)
# Debian/Ubuntu
sudo apt install tabix
# or conda, which has every tool above
conda install -c bioconda -c conda-forge htslib ncbi-datasets-cli
# or Linux x86_64 binaries for the tools apt lacks
wget -P ~/.local/bin \
https://ftp.ncbi.nlm.nih.gov/pub/datasets/command-line/v2/linux-amd64/datasets \
https://ftp.ncbi.nlm.nih.gov/pub/datasets/command-line/v2/linux-amd64/dataformat
chmod +x ~/.local/bin/datasets ~/.local/bin/dataformatThe data
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. The E. coli section
uses E. coli and Shigella assemblies anchored on K-12 MG1655, all pinned by
accession in its build script.
The tracks below read local files that the build scripts write, so run them first, or run An ortholog table joined on gene names over RefSeq accessions of your own. Each script fetches the annotations and reports it needs for every genome itself.
| What it is | File |
|---|---|
| Human, GRCh38.p14 | GCF_000001405.40_GRCh38.p14/ |
| Chimpanzee | GCF_028858775.2_NHGRI_mPanTro3-v2.1_pri/ |
| Bonobo | GCF_029289425.2_NHGRI_mPanPan1-v2.1_pri/ |
| Gorilla | GCF_029281585.2_NHGRI_mGorGor1-v2.1_pri/ |
| Sumatran orangutan | GCF_028885655.2_NHGRI_mPonAbe1-v2.1_pri/ |
| Bornean orangutan | GCF_028885625.2_NHGRI_mPonPyg2-v2.1_pri/ |
| Siamang | GCF_028878055.3_NHGRI_mSymSyn1-v2.1_pri/ |
| Rhesus macaque, T2T-MMU8v2.0 | GCF_049350105.2_T2T-MMU8v2.0/ |
| E. coli K-12 MG1655 | GCF_000005845.2_ASM584v2/ |
An ortholog table joined on gene names
An ortholog table in the .blocks shape has one row per orthologous group, one
column per genome and a gene id in each cell. NCBI's eukaryotic annotation
pipeline names a gene after its ortholog, so human TP53 is chimpanzee TP53
and gorilla TP53, and for genomes it annotated 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.zipsymbols_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
# the ortholog of that gene, 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.gzsymbols_to_blocks.py joins on the case-folded symbol and handles these cases:
- A gene named with an NCBI
LOCplaceholder joins nothing. RefSeq names the salivary amylase copies that way in the other primates. - A symbol shared by several genes in one genome gets a row per copy, because a link joins one gene to one gene. The main case is the pseudoautosomal genes, annotated on both X and Y.
- Open reading frame genes spell differently in the apes (human C1orf35 is chimp C1H1orf35), and the helper reads that back.
The helper prints how much of each column it filled.
Setting up each lane's assembly from its hub
Each genome is also a hub on genomes.jbrowse.org.
A hub's config.json holds a whole JBrowse assembly: the 2bit sequence, an
alias table that resolves the NC_ names in the BEDs, and the NCBI RefSeq
genes. Each lane takes its assembly entry and gene track from its hub, so the
ortholog table is the one file built here:
# a GenArk hub path is the accession cut into threes
curl -fO https://jbrowse.org/hubs/genark/GCF/028/858/775/GCF_028858775.2/config.jsonThe build relabels the lane and adds the short name as an alias, so a session
can still say chimp:
{
"name": "GCF_028858775.2",
"displayName": "Chimpanzee (NHGRI_mPanTro3-v2.1)",
"aliases": ["chimp"],
"sequence": {
"type": "ReferenceSequenceTrack",
"trackId": "GCF_028858775.2-ReferenceSequenceTrack",
"adapter": {
"type": "TwoBitAdapter",
"uri": "https://hgdownload.soe.ucsc.edu/hubs/GCF/028/858/775/GCF_028858775.2/GCF_028858775.2.2bit",
"chromSizes": "https://hgdownload.soe.ucsc.edu/hubs/GCF/028/858/775/GCF_028858775.2/GCF_028858775.2.chrom.sizes.txt"
}
},
"refNameAliases": {
"adapter": {
"type": "RefNameAliasAdapter",
"refNameColumnHeaderName": "ucsc",
"uri": "https://hgdownload.soe.ucsc.edu/hubs/GCF/028/858/775/GCF_028858775.2/GCF_028858775.2.chromAlias.txt"
}
}
}refNameColumnHeaderName makes the UCSC names canonical, so the lane headers
read chr19. Human is the exception: UCSC serves GRCh38 as hg38 with no
GenArk hub, so the human lane is hg38:
Goes in the assemblies array of config.json. See Assemblies.
{
"name": "hg38",
"uri": "https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz",
"refNameAliases": {
"uri": "https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/hg38_aliases.txt"
},
"cytobands": "https://jbrowse.org/genomes/GRCh38/cytoBand.txt"
}jbrowse add-assembly https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz \
--name hg38 \
--refNameAliases https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/hg38_aliases.txt \
--config '{"cytobands":"https://jbrowse.org/genomes/GRCh38/cytoBand.txt"}'hg38 is one of the genomes JBrowse Desktop hosts, with gene tracks already set up: on the start screen click Show all available genomes and pick it. To load the files of this config instead:
In JBrowse Desktop, Open new genome on the start screen (or File → Open genome... in a session), then Open from a URL and paste, one per line:
https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz
https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz.fai
https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz.gziJBrowse reads the format off the file name. Then fill in:
- Genome name:
hg38 - refName aliases (under More options):
https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/hg38_aliases.txt - cytobands (under More options):
https://jbrowse.org/genomes/GRCh38/cytoBand.txt
Each lane draws gene models from a gene track on its assembly. For your own annotation, add one per genome from a bgzipped, tabix-indexed GFF3 (prep) that uses the assembly's refNames:
Goes in the tracks array of config.json. See Tracks.
{
"type": "FeatureTrack",
"trackId": "chimp_genes",
"name": "chimp genes",
"assemblyNames": ["GCF_028858775.2"],
"adapter": {
"type": "Gff3TabixAdapter",
"uri": "chimp.sorted.gff3.gz"
}
}jbrowse add-track chimp.sorted.gff3.gz \
--trackId chimp_genes \
--name "chimp genes" \
--assemblyNames GCF_028858775.2 \
--load copyIn JBrowse Desktop, or in any running JBrowse Web session, open a view on this track’s assembly, then File → Open track... and, in Add a track from file or URL, enter:
- Main file:
chimp.sorted.gff3.gz
Click Next. JBrowse reads the adapter and track type off the file name. Then fill in:
- Track name:
chimp genes - Assembly:
GCF_028858775.2
Click Add.
chimp.sorted.gff3.gz is relative to a config.json. Replace it with its URL or its path on this computer.
Loading the multi-genome ortholog track
One SyntenyTrack names every assembly, and blockAssemblies and
bedLocations hold one entry per table column, in the order the helper printed.
In grammar-of-graphics terms, the display's
encoding.color is a color scale, and the cluster field maps each gene's
ortholog group, named by its gene symbol, to a color:
- A conserved gene is one color down the whole stack, and a lane missing it breaks the column.
- A gene no group claims is grey.
- A key naming the groups appears in the top right once the window holds few enough to list. Untick Show → Legend in the track menu to hide it.
Goes in the tracks array of config.json. See Tracks.
{
"type": "SyntenyTrack",
"trackId": "primate_orthologs",
"name": "Primate orthologs by gene symbol (8 genomes, RefSeq)",
"assemblyNames": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"uri": "primates.blocks.gz",
"blockAssemblies": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"bedLocations": [
"human.bed.gz",
"chimp.bed.gz",
"bonobo.bed.gz",
"gorilla.bed.gz",
"sumatran.bed.gz",
"bornean.bed.gz",
"siamang.bed.gz",
"macaque.bed.gz"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"encoding": { "color": "cluster" }
}
]
}jbrowse add-track-json '{
"type": "SyntenyTrack",
"trackId": "primate_orthologs",
"name": "Primate orthologs by gene symbol (8 genomes, RefSeq)",
"assemblyNames": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"uri": "primates.blocks.gz",
"blockAssemblies": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"bedLocations": [
"human.bed.gz",
"chimp.bed.gz",
"bonobo.bed.gz",
"gorilla.bed.gz",
"sumatran.bed.gz",
"bornean.bed.gz",
"siamang.bed.gz",
"macaque.bed.gz"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"encoding": { "color": "cluster" }
}
]
}'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": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"adapter": {
"type": "MCScanBlocksAdapter",
"uri": "primates.blocks.gz",
"blockAssemblies": [
"hg38",
"GCF_028858775.2",
"GCF_029289425.2",
"GCF_029281585.2",
"GCF_028885655.2",
"GCF_028885625.2",
"GCF_028878055.3",
"GCF_049350105.2"
],
"bedLocations": [
"human.bed.gz",
"chimp.bed.gz",
"bonobo.bed.gz",
"gorilla.bed.gz",
"sumatran.bed.gz",
"bornean.bed.gz",
"siamang.bed.gz",
"macaque.bed.gz"
]
},
"displays": [
{
"type": "MultiWaySyntenyDisplay",
"encoding": { "color": "cluster" }
}
]
}primates.blocks.gz, human.bed.gz, chimp.bed.gz, bonobo.bed.gz, gorilla.bed.gz, sumatran.bed.gz, bornean.bed.gz, siamang.bed.gz, macaque.bed.gz are relative to a config.json. Replace each with its URL or its path on this computer.
That shows it in the linear view. For the synteny view, open Add → Linear synteny view, pick the track under Quick start, and click Launch.
Reading the TP53 neighbourhood across eight primates
In a linear genome view on human, the track draws a lane per primate under the
human axis, each in that genome's coordinates. Each lane's header names the
chromosome and span, with [rev] where the lane runs against human
(lane headers).
The ribbons color by strand by default (Color by → Strand, under Ribbons
on the track menu): each ribbon shows whether its two lanes agree in
orientation. The session below opens the TP53 neighbourhood:
Goes at the top level of config.json, replacing any defaultSession there. See Default session.
{
"defaultSession": {
"name": "TP53 neighbourhood across eight primates",
"views": [
{
"type": "LinearGenomeView",
"assembly": "hg38",
"loc": "chr17:7,400,000-7,700,000",
"tracks": [
{
"trackId": "primate_orthologs",
"type": "MultiWaySyntenyDisplay",
"ribbons": { "encoding": { "color": "strand" } },
"height": 620
}
]
}
]
}
}jbrowse set-default-session --session - << 'EOF'
{
"name": "TP53 neighbourhood across eight primates",
"views": [
{
"type": "LinearGenomeView",
"assembly": "hg38",
"loc": "chr17:7,400,000-7,700,000",
"tracks": [
{
"trackId": "primate_orthologs",
"type": "MultiWaySyntenyDisplay",
"ribbons": { "encoding": { "color": "strand" } },
"height": 620
}
]
}
]
}
EOFNavigate to chr17:34,000,000-38,000,000 on 17q, where the strand color
separates forward blocks from reversed ones.
Human chromosome 2 as two fused ape chromosomes
Human chromosome 2 is two ape chromosomes joined end to end. To see the join:
- Open human over chimpanzee from Add → Linear synteny view, with
primate_orthologsbetween them - Navigate the human row to
chr2and the chimpanzee row tochr12andchr13, the two chromosomes that hold its halves - Pick Target in the palette button menu, which paints each ribbon by the chimpanzee chromosome it maps to
In the multi-lane view, a lane whose genome holds the window's genes on two contigs shows the contig with more of them and names the other in its header. Show ⟨contig⟩ in this lane on the header menu swaps the lane onto it.
Joining bacterial annotations (E. coli)
Genes NCBI left unnamed, the LOC genes above, are common in a bacterial
annotation: RefSeq's Prokaryotic Genome Annotation Pipeline (PGAP) writes a
gene's locus tag into its Name when it has no symbol, and renames genes
between releases. Older PGAP runs gave every gene a locus tag and no symbol, so
counting the named genes in an annotation shows whether a genome will join
before anything is built:
gzip -dc strain.gff.gz | awk -F'\t' '$3 == "gene" && $9 ~ /;gene=/' | wc -lThe join is the same command with three flags for PGAP:
# --unnamed swaps the LOC pattern for the locus-tag shape PGAP falls back to,
# so a hypothetical protein joins nothing; --merge-cited joins a gene PGAP
# renamed to the gene it was annotated from; --pseudogenes keeps a gene NCBI
# writes as a pseudogene feature, such as K-12's wbbL
python3 symbols_to_blocks.py --anchor MG1655 -o ecoli.blocks --unnamed '_RS[0-9]+$' --merge-cited --pseudogenes \
MG1655=MG1655.gff.gz Sakai=Sakai.gff.gz CFT073=CFT073.gff.gz Sflexneri_301=Sflexneri_301.gff.gzPGAP writes on each CDS the protein it annotated the gene from
(similar to AA sequence:RefSeq:NP_416533.1, the K-12 gnd protein), and
--merge-cited reads that note to join K-12's gnd to the gndA most strains
carry.
A lane draws its genes from the BED and the gene track, so a strain's assembly
is its chromosome's name and length. The build writes <strain>.chrom.sizes,
one tab-separated line, and loads it as a ChromSizesAdapter:
Goes in the assemblies array of config.json. See Assemblies.
{ "name": "MG1655", "uri": "MG1655.chrom.sizes" }jbrowse add-assembly MG1655.chrom.sizes \
--name MG1655 \
--load copyIn JBrowse Desktop, Open new genome on the start screen (or File → Open genome... in a session), then Open from a URL and paste:
MG1655.chrom.sizesJBrowse reads the format off the file name. Then fill in:
- Genome name:
MG1655
MG1655.chrom.sizes is relative to a config.json. Replace it with its URL or its path on this computer.
Each strain then takes a gene track like the chimpanzee one above, and the
SyntenyTrack lists strains where the primate track lists primates. The
O-antigen cluster between galF and gnd holds the genes that build a strain's
surface sugar chain, a different set in each serotype. The session opens it on
the hosted config, which has every strain, with height set to fit the stack:
Goes at the top level of config.json, replacing any defaultSession there. See Default session.
{
"defaultSession": {
"name": "The O-antigen cluster across 44 genomes",
"views": [
{
"type": "LinearGenomeView",
"assembly": "MG1655",
"loc": "NC_000913.3:2,095,000-2,115,000",
"tracks": [
{
"trackId": "ecoli_orthologs",
"type": "MultiWaySyntenyDisplay",
"height": 1500
}
]
}
]
}
}jbrowse set-default-session --session - << 'EOF'
{
"name": "The O-antigen cluster across 44 genomes",
"views": [
{
"type": "LinearGenomeView",
"assembly": "MG1655",
"loc": "NC_000913.3:2,095,000-2,115,000",
"tracks": [
{
"trackId": "ecoli_orthologs",
"type": "MultiWaySyntenyDisplay",
"height": 1500
}
]
}
]
}
EOFRead the lanes from the top:
- The K-12 derivatives sort to the top, since lanes stack by how many of the window's genes they place on one contig, and have the cluster gene for gene.
- Below them the flanks chain down every lane: galF on one side, gnd (gndA in most lanes), ugd and wzzB on the other.
- The interior is grey, except the rfb genes, wzx and wzy, which join wherever a strain has them.
symbols_to_blocks.py gives a symbol K-12 lacks a row of its own, so vioB is
one color down IAI39 and the BL21 lanes. A gene PGAP left under its locus tag,
such as ECOLC_RS24020 in the ATCC_8739 lane, has no row. Most of the grey is
each serotype's sugar genes, which have no K-12 counterpart to join by name;
OrthoFinder groups genes by protein
sequence, which joins the ones the annotations named differently.
Reproduce it end to end
The primate script needs the tools under Prerequisites and works in three steps:
- Download the eight annotations and sequence reports, and keep the genes on the assembled chromosomes each report names, so no lane has a gene on an unplaced scaffold.
- Join the annotations on gene symbol into one table anchored on human.
- Write the config from each genome's hub entry and gene track, plus the ortholog track.
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_primate_orthologs.sh
bash build_primate_orthologs.shThe E. coli script also needs htslib, and works in three steps:
- Download each genome's annotation, sequence report and assembly report, name each lane after the report's strain field, and drop a genome the report calls neither E. coli nor Shigella.
- Keep each genome's longest sequence as its chromosome, so a lane follows one contig and no plasmid, and sort and index the GFF3 as in the web quickstart.
- Join the annotations on gene symbol with the PGAP flags, and write the config.
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_ecoli_orthologs.sh
bash build_ecoli_orthologs.shSee also
- Tutorial: Synteny from an ortholog table (grape, peach, cacao)
- Tutorial: Synteny visualization (OrthoFinder orthogroups)
- Tutorial: Synteny visualization (all-vs-all minimap2)
- Tutorial: Synteny from a Cactus alignment (T2T apes)
- Tutorial: Pangenome (pggb)
External links
- NCBI Datasets: https://www.ncbi.nlm.nih.gov/datasets/
Citations
- Yoo et al. (2025). Complete sequencing of ape genomes
- Li et al. (2021). RefSeq: expanding the Prokaryotic Genome Annotation Pipeline reach with protein family model curation
- O'Leary et al. (2016). Reference sequence (RefSeq) database at NCBI: current status, taxonomic expansion, and functional annotation
Feedback on this tutorial is welcome: contact us.