Synteny on a circle (human and mouse)
We lay the human and mouse chromosomes around one circle and draw every block of
UCSC's hg38-to-mm39 liftOver chain as a ribbon between the stretch it covers in
each genome, so where the autosomes have been shuffled and where the X has not
is one picture. autoDiagonalize orders the mouse arc to follow the human one,
a gene density ring per genome sits inside the ideogram, and the page ends by
reading one ribbon and one ring value back out of the PIF and bigWig they came
from. The circle is a circular genome view opened on two assemblies at once,
which a session spec does and the import form does not.
Prerequisites
- nothing to install to read along: the config, the blocks and the density bigWig are hosted, and every figure carries a live link
- a JBrowse to open them in (Web or Desktop)
- to build the files for another pair of genomes,
chain2paf, htslib (bgzip,tabix),bedGraphToBigWigandbigWigToBedGraphfrom the UCSC utilities, andnodefor the JBrowse CLI
chain2paf is a single binary from
its releases page, and
it is the one converter that turns a UCSC chain into a PAF row per chain with
the CIGAR intact.
Where the data comes from
UCSC's hg38-to-mm39 liftOver chain (Kent et al. 2003), the RefSeq curated gene sets of both genomes from jbrowse.org's copies of the UCSC hubs, and the files the build script makes of them, rehosted so the figures open without the build.
- the chain: https://hgdownload.soe.ucsc.edu/goldenPath/hg38/liftOver/hg38ToMm39.over.chain.gz
- the same chain as an indexed PAF, every row, which the last section queries: https://jbrowse.org/ucsc/hg38/liftOver/hg38ToMm39.over.pif.gz
- the hub configs the two assemblies are taken from: https://jbrowse.org/ucsc/hg38/config.json and https://jbrowse.org/ucsc/mm39/config.json
- RefSeq curated genes, hg38: https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz
- RefSeq curated genes, mm39: https://jbrowse.org/ucsc/mm39/ncbiRefSeqCurated.gff.gz
- the chain cut to its blocks, as the PIF the ribbons draw from: https://jbrowse.org/demos/circular_synteny/hg38ToMm39.blocks.pif.gz
- both genomes' gene density in one bigWig, the ring: https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw
- the finished config, both assemblies and the four tracks: https://jbrowse.org/demos/circular_synteny/config.json
Two genomes on one circle
A whole-genome alignment between two species is usually read as a dotplot or as lanes under one locus. The Circos-style picture asks a different question: for each chromosome of one genome, which chromosomes of the other carry its sequence, and how much of each. Human and mouse are the textbook case. The autosomes have been cut and rejoined many times since the two lineages split, and the X chromosome has not, so a human autosome should fan out across several mouse chromosomes while the two X chromosomes hold one bundle between them. That expectation is the control the figures are read against.
Cutting the chain to its blocks
A liftOver chain set holds a few hundred chains that cover the genome and tens of thousands of short ones, most of them repeats and gene copies, and the circle draws every row it is given. So the chain becomes a PAF, the PAF is cut to the rows of 100 kb and over, and those rows are indexed as a PIF without their CIGARs: a ribbon is drawn between a row's two spans and never reads the alignment inside, so dropping column 13 onward keeps the file small enough to fetch whole.
# chain2paf writes one PAF row per chain, the genome the chain lifts TO as the
# query and the chain's reference as the target
gzip -dc hg38ToMm39.over.chain.gz > hg38ToMm39.over.chain
chain2paf -i hg38ToMm39.over.chain > hg38ToMm39.paf
# column 11 is the row's aligned length with its gaps; NF=12 drops the CIGAR
awk -F'\t' -v OFS='\t' -v min=100000 '$11 >= min {NF=12; print}' hg38ToMm39.paf > hg38ToMm39.blocks.paf
# --no-coarse: the coarse tier folds a CIGAR, and these rows carry none
jbrowse make-pif hg38ToMm39.blocks.paf --out hg38ToMm39.blocks.pif.gz --no-coarse
The track names the file and its two assemblies, query first. A chain's query is the genome it lifts to, so for hg38ToMm39 that is mouse:
{
"type": "SyntenyTrack",
"trackId": "hg38ToMm39_blocks",
"name": "hg38 vs mm39 (liftOver chains of 100 kb and over)",
"assemblyNames": ["mm39", "hg38"],
"adapter": {
"type": "PairwiseIndexedPAFAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.blocks.pif.gz",
"queryAssembly": "mm39",
"targetAssembly": "hg38"
}
}
jbrowse add-track-json '{
"type": "SyntenyTrack",
"trackId": "hg38ToMm39_blocks",
"name": "hg38 vs mm39 (liftOver chains of 100 kb and over)",
"assemblyNames": ["mm39", "hg38"],
"adapter": {
"type": "PairwiseIndexedPAFAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.blocks.pif.gz",
"queryAssembly": "mm39",
"targetAssembly": "hg38"
}
}'
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": "hg38ToMm39_blocks",
"name": "hg38 vs mm39 (liftOver chains of 100 kb and over)",
"assemblyNames": ["mm39", "hg38"],
"adapter": {
"type": "PairwiseIndexedPAFAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.blocks.pif.gz",
"queryAssembly": "mm39",
"targetAssembly": "hg38"
}
}
The circle
The circular view's assembly takes a list, and each assembly lays its
chromosomes out in turn: hg38 takes the first arc of the circle and mm39 the
next, and a ribbon crosses between them. displayedRegionNames is resolved
against each assembly separately, so one list of chromosome names keeps both
genomes' unplaced contigs off the circle. autoDiagonalize makes the result
readable; the next section explains how. The import form opens a single
assembly, so a two-assembly circle is written as a session:
{
"defaultSession": {
"name": "hg38 and mm39 on one circle",
"views": [
{
"type": "CircularView",
"assembly": ["hg38", "mm39"],
"autoDiagonalize": true,
"displayedRegionNames": [
"chr1",
"chr2",
"chr3",
"chr4",
"chr5",
"chr6",
"chr7",
"chr8",
"chr9",
"chr10",
"chr11",
"chr12",
"chr13",
"chr14",
"chr15",
"chr16",
"chr17",
"chr18",
"chr19",
"chrX"
],
"height": 780,
"tracks": ["hg38ToMm39_blocks"]
}
]
}
}
jbrowse set-default-session --session - << 'EOF'
{
"name": "hg38 and mm39 on one circle",
"views": [
{
"type": "CircularView",
"assembly": ["hg38", "mm39"],
"autoDiagonalize": true,
"displayedRegionNames": [
"chr1",
"chr2",
"chr3",
"chr4",
"chr5",
"chr6",
"chr7",
"chr8",
"chr9",
"chr10",
"chr11",
"chr12",
"chr13",
"chr14",
"chr15",
"chr16",
"chr17",
"chr18",
"chr19",
"chrX"
],
"height": 780,
"tracks": ["hg38ToMm39_blocks"]
}
]
}
EOF
The two genomes share their chromosome names, so the arcs are labelled identically around the circle: the human chromosomes run clockwise from the top and the mouse chromosomes follow, and the view's title bar names the two in that order. Every ribbon is one flat translucent fill, and a reverse alignment reads as a twist between its two ends rather than as a second color.
Ordering the second genome
Both arcs run the same way round the circle, so a mouse chromosome laid out in its own contig order sits opposite the human chromosome it does not align to, and each ribbon crosses the middle to reach its partner.
autoDiagonalize is the reorder the
linear synteny view and the
dotplot run on open: each mouse chromosome
takes the human chromosome it shares the most aligned bases with, and the mouse
arc follows that order. The circle lays it out mirrored, so the mouse arc's
coordinate falls where the human arc's rises. Chords between two arcs cross only
where both sides rise together, so the ribbons come out as a band. A mouse
chromosome that runs antiparallel to its human partner is drawn the other way
round again, and the twists left on the figure are the inversions.
Re-order chromosomes in the view's menu runs the same pass on demand, with a progress bar and a cancel; re-running it on a circle that is already ordered moves nothing.
The X chromosome as the control
Three chromosomes of each genome make the control readable. The two autosomes cross-wire between the genomes, and the X ribbons run between the two X arcs with nothing joining either X to an autosome.
The chain rows shorter than the cut are where the X does touch the autosomes: the full liftOver PIF holds a few hundred of them between human chrX and mouse autosomes, each a repeat or a retrocopy a few hundred bases long, and the cut at 100 kb keeps them off the circle.
Gene density as a ring
Any track that draws in the linear genome view draws on the circle as a ring
inside the ideogram, so the question of whether the conserved blocks are the
gene-rich stretches is one more track. jbrowse make-density counts a GFF3's
top-level features per bin into a bigWig, so a gene is one count however many
transcripts hang under it.
A ring on a two-genome circle is drawn by one track that names both assemblies,
so both genomes' densities go into one bigWig: each contig is prefixed with its
genome before counting, and each assembly's alias table gains that spelling as
an alias of the bare name, which is how the track's chr1 request from the
mouse arc reaches mm39.chr1 and the human arc's reaches hg38.chr1.
# one chrom.sizes and one GFF3 for the pair, every contig prefixed by genome
for g in hg38 mm39; do
awk -v g="$g" -F'\t' -v OFS='\t' '{print g "." $1, $2}' "$g.chrom.sizes" >> hg38ToMm39.chrom.sizes
gzip -dc "$g.genes.gff.gz" | awk -v g="$g" -F'\t' -v OFS='\t' '/^#/ {next} {$1 = g "." $1; print}' >> hg38ToMm39.genes.gff
# the hub's alias table, plus the prefixed name as one more alias per row
awk -v g="$g" -F'\t' -v OFS='\t' '/^#/ {print; next} {print $0, g "." $1}' "$g.hub.chromAlias.txt" > "$g.chromAlias.txt"
done
bgzip -f hg38ToMm39.genes.gff
# a whole-genome ring is megabases per pixel, so the bin is 100 kb
jbrowse make-density hg38ToMm39.genes.gff.gz --chrom-sizes hg38ToMm39.chrom.sizes --bin 100000
The track is a quantitative track naming both assemblies, and on the circle it is opened as a density strip whose colour is the average over each pixel's bins:
{
"type": "QuantitativeTrack",
"trackId": "hg38ToMm39_gene_density",
"name": "hg38 and mm39 gene density (RefSeq curated genes per 100 kb)",
"assemblyNames": ["hg38", "mm39"],
"adapter": {
"type": "BigWigAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw"
},
"displays": [
{
"type": "LinearWiggleDisplay",
"displayId": "hg38ToMm39_gene_density-LinearWiggleDisplay",
"defaultRendering": "density",
"summaryScoreMode": "avg",
"height": 40
}
]
}
jbrowse add-track-json '{
"type": "QuantitativeTrack",
"trackId": "hg38ToMm39_gene_density",
"name": "hg38 and mm39 gene density (RefSeq curated genes per 100 kb)",
"assemblyNames": ["hg38", "mm39"],
"adapter": {
"type": "BigWigAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw"
},
"displays": [
{
"type": "LinearWiggleDisplay",
"displayId": "hg38ToMm39_gene_density-LinearWiggleDisplay",
"defaultRendering": "density",
"summaryScoreMode": "avg",
"height": 40
}
]
}'
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": "QuantitativeTrack",
"trackId": "hg38ToMm39_gene_density",
"name": "hg38 and mm39 gene density (RefSeq curated genes per 100 kb)",
"assemblyNames": ["hg38", "mm39"],
"adapter": {
"type": "BigWigAdapter",
"uri": "https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw"
},
"displays": [
{
"type": "LinearWiggleDisplay",
"displayId": "hg38ToMm39_gene_density-LinearWiggleDisplay",
"defaultRendering": "density",
"summaryScoreMode": "avg",
"height": 40
}
]
}
Rings stack inward from the ideogram in the order the view's tracks lists
them, and the ribbons draw inside the innermost ring, so the density entry goes
before the synteny track:
{
"defaultSession": {
"name": "hg38 and mm39 with gene density rings",
"views": [
{
"type": "CircularView",
"assembly": ["hg38", "mm39"],
"autoDiagonalize": true,
"displayedRegionNames": ["chr1", "chr2", "chrX"],
"height": 780,
"tracks": [
{
"trackId": "hg38ToMm39_gene_density",
"type": "LinearWiggleDisplay",
"defaultRendering": "density",
"summaryScoreMode": "avg",
"height": 40
},
"hg38ToMm39_blocks"
]
}
]
}
}
jbrowse set-default-session --session - << 'EOF'
{
"name": "hg38 and mm39 with gene density rings",
"views": [
{
"type": "CircularView",
"assembly": ["hg38", "mm39"],
"autoDiagonalize": true,
"displayedRegionNames": ["chr1", "chr2", "chrX"],
"height": 780,
"tracks": [
{
"trackId": "hg38ToMm39_gene_density",
"type": "LinearWiggleDisplay",
"defaultRendering": "density",
"summaryScoreMode": "avg",
"height": 40
},
"hg38ToMm39_blocks"
]
}
]
}
EOF
A mirrored arc's ring is mirrored with it, so a bin sits under the stretch of ideogram it belongs to whichever way round that chromosome is drawn.
Reading a ribbon back
Hovering a ribbon fills it in the hover color, and the browser's tooltip names the alignment: its span in each genome and which way round the two read. The widest ribbon on the three-chromosome circle is the X block that runs reverse between the two genomes, which is why it twists.
The tooltip's two loci are the row the PIF holds. tabix returns it from the
blocks file at the human coordinate the tooltip starts at, and from the full
liftOver PIF too, since the cut removed rows and changed none:
# the t prefix asks for the row in hg38 coordinates; q would ask in mm39's
tabix https://jbrowse.org/demos/circular_synteny/hg38ToMm39.blocks.pif.gz tchrX:10447551-10447552
tabix https://jbrowse.org/ucsc/hg38/liftOver/hg38ToMm39.over.pif.gz tchrX:10447551-10447552
The row's strand column says -, and its query span on mouse chrX is the other
end of the ribbon. The ring reads back the same way: bigWigToBedGraph prints
the bins under any stretch of either arc, addressed by the prefixed contig name
the bigWig carries.
# the first 100 kb of the X block, then the start of human chr19
bigWigToBedGraph -chrom=hg38.chrX -start=10447550 -end=10547550 \
https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw stdout
bigWigToBedGraph -chrom=hg38.chr19 -start=0 -end=300000 \
https://jbrowse.org/demos/circular_synteny/hg38ToMm39.genes.gff.density.bw stdout
A bin's value is the count of genes starting in it, so the pale X ring and the dark chr19 ring are the two counts side by side.
Your own pair of genomes
The script takes any UCSC pair by name, and the files it writes are what a reader's own pair has to satisfy:
- an alignment as a PIF, sorted and tabix-indexed: a chain through
chain2pafas above, or a PAF straight from minimap2 or wfmash, throughjbrowse make-pif; whichever way, the synteny track'sassemblyNamesis[query, target]. The reorder needs nothing beyond that: it reads the same file - both assemblies declared in the config, from a hub entry as here or from
jbrowse add-assembly genome.fa - one bigWig per ring, naming the ring's assembly; for a ring that covers both genomes, one bigWig with prefixed contigs and an alias per assembly as above
Reproduce it end to end
The script fetches the chain, both hub configs and both gene sets, converts and
cuts the chain, builds the density bigWig and writes the config. TARGET and
QUERY pick another UCSC pair, MIN_BLOCK moves the cut, and BASE_URL is the
prefix the finished files will be served under; see
Prerequisites.
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_circular_synteny.sh
bash build_circular_synteny.sh
See also
- Circular genome view
- Synteny track
- Synteny from liftOver chains (hg38 and eight vertebrates)
- Synteny visualization (pairwise minimap2)
- Gene density and transposon density along a chromosome
References
- Kent WJ, Baertsch R, Hinrichs A, Miller W, Haussler D. Evolution's cauldron: duplication, deletion, and rearrangement in the mouse and human genomes. Proc Natl Acad Sci USA (2003). https://doi.org/10.1073/pnas.1932072100
- Ohno S. Sex Chromosomes and Sex-Linked Genes. Springer (1967), the argument that the mammalian X chromosome's gene content is conserved across species.
- Krzywinski M, Schein J, Birol I, et al. Circos: an information aesthetic for comparative genomics. Genome Research 19:1639-1645 (2009). https://doi.org/10.1101/gr.092759.109
Feedback on this tutorial is welcome: contact us.