Pangenome (pggb)
2 videos in this tutorial
TL;DR: build a five-strain E. coli graph with pggb, then load its linear projections (synteny, pangenome variants, whole-genome MAF, depth and per-strain presence) as ordinary JBrowse tracks on the K12 axis, and draw the graph itself beside them.
The graph view is a beta plugin, and this tutorial covers experimental ideas. We welcome your feedback.
Prerequisites
dockerorsingularity, for the pggb image, which also carries odgisamtoolsbedGraphToBigWig(UCSC kentUtils)python3- htslib (
bgzip,tabix) node, for the JBrowse CLI- the NCBI
datasetsCLI, to fetch the RefSeq genomes for the whole build rather than the steps on this page unzip, to unpack them for the same whole build- the GraphGenomeView plugin, for the graph itself; every other track here is a built-in type
On Debian/Ubuntu, apt install samtools tabix unzip python3 covers four of
those. Docker installs from
docs.docker.com; the NCBI datasets
CLI and bedGraphToBigWig are each a
single-binary download; and node
comes from nodejs.org. Everything else runs inside the
pggb image.
Where the data comes from
Five E. coli RefSeq assemblies, fetched by accession with the NCBI datasets CLI and concatenated into one PanSN-named FASTA for pggb.
- K12: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/005/845/GCF_000005845.2_ASM584v2/
- Sakai: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/008/865/GCF_000008865.2_ASM886v2/
- CFT073: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/007/445/GCF_000007445.1_ASM744v1/
- NCTC86: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/002/007/705/GCF_002007705.1_ASM200770v1/
- IAI39: https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/026/345/GCF_000026345.1_ASM2634v1/
- nanopore reads from an unrelated isolate, E. coli E146, mapped straight onto K12 with no graph: https://www.ebi.ac.uk/ena/browser/view/DRR193901
- the pggb and minigraph graphs' segments, links and bubbles, tabix-indexed and rehosted so the graph genome view figures load with no local build: https://jbrowse.org/demos/ecoli_pangenome/
The linear projections
A pangenome graph collapses many genomes into one structure: shared sequence is a single path every sample walks, and where samples differ the path branches. pggb, Minigraph-Cactus, and progressiveCactus build these graphs, and odgi manipulates them. Annotation-based bacterial pangenome tools (Panaroo, Roary, PPanGGOLiN) give a gene table instead; a gene cluster no reference carries has no coordinate on the K12 axis. The gene-symbol lanes draw that kind of table without flattening it — one lane per genome at its own coordinates, and these five strains sit among its forty-four.
Most of what JBrowse draws are the graph's linear projections: the graph flattened onto one reference genome's coordinates. Every builder emits them:
| Projection | What it shows | From the graph | JBrowse track |
|---|---|---|---|
| Synteny | The blocks each pair of genomes shares | odgi untangle, halSynteny | synteny track |
| Pangenome variants | Every difference the graph calls, across all samples | pggb -V, cactus-pangenome --vcf, vg deconstruct | multi-sample variant track |
| Whole-genome alignment | The multiple alignment, column by column | pggb -M, hal2maf | User guide: MAF track |
| Pangenome depth | How many genomes cover each reference base (core/accessory) | odgi depth, odgi pav | quantitative track |
This tutorial builds a five-strain E. coli pangenome with pggb, loads each projection, and draws the graph itself, on the same five genomes as the all-vs-all synteny tutorial.
Building the graph with pggb
pggb takes one FASTA of all the genomes,
PanSN-named
sample#haplotype#contig. The prefix before the last # is how wfmash avoids
aligning a genome to itself and how -V assigns VCF samples. Concatenate the
five strains (haplotype 1, these being haploid) and index the result,
chromosomes only:
for strain in K12 Sakai CFT073 NCTC86 IAI39; do
awk -v s="$strain" '/^>/{print ">" s "#1#chr"; next} {print}' "$strain.fa"
done > all.fa
bgzip all.fa
samtools faidx all.fa.gz
The image pins all five tools of the pipeline at once (pggb's
installation docs
cover the alternatives) and carries odgi,
which later sections reuse, so wrap the docker run once. -V K12:10000
decomposes the graph into a VCF against the K12 path and -M writes the MAF:
in_pggb() {
docker run --rm -u "$(id -u):$(id -g)" -w /data -v "$PWD":/data \
ghcr.io/pangenome/pggb:202603141454453ade6b "$@"
}
in_pggb pggb -i /data/all.fa.gz -o /data/pggb \
-n 5 -c 4 -p 90 -s 5000 -V K12:10000 -M -t "$(nproc)"
-nis the number of haplotypes,-pthe minimum alignment identity and-sthe segment length;-p 90 -s 5000suits a bacterial pangenome.-cis the number of mappings wfmash keeps per segment and defaults to1, so it has to be raised alongside-nor the graph comes out under-connected.- Under singularity,
singularity exec --bind "$PWD":/data --pwd /data docker://<image>replaces the wrapper body.
Five bacterial chromosomes are minutes on a laptop.
pggb runs four tools in turn:
- wfmash aligns the genomes all-vs-all
- seqwish induces the graph
- smoothxg normalizes it
- gfaffix collapses shared prefixes
Then odgi draws the visualizations and vg deconstruct runs the -V step.
The output directory holds the graph (*.smooth.final.gfa and its .og), the
all-vs-all PAF, both VCF tiers, the MAF, and pggb's own odgi viz and
odgi layout renderings unless you passed -v.
Resolve the graph's two spellings once; the glob has to expand on the host,
since /data/*.gfa inside the container is a literal:
gfa=$(ls pggb/*.smooth.final.gfa)
og=$(ls pggb/*.smooth.final.og)
.og is odgi's own serialization of the graph, which every odgi command below
reads. The tabix index is built from the GFA, whose P and W lines it reads as
text.
Synteny projection
Two files answer this, a track each.
The alignment the graph was induced from
pggb's first step is a wfmash all-vs-all PAF, the same input the
all-vs-all synteny tutorial loads. Index it
with jbrowse make-pif and load it with an
MultiGenomeIndexedPAFAdapter:
cp pggb/*.alignments.wfmash.paf ecoli_pggb_ava.paf
jbrowse make-pif ecoli_pggb_ava.paf # -> ecoli_pggb_ava.pif.gz (+ .tbi)
{
"type": "SyntenyTrack",
"trackId": "ecoli_pggb_ava",
"name": "pggb graph: all-vs-all synteny (wfmash)",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"adapter": {
"type": "MultiGenomeIndexedPAFAdapter",
"uri": "ecoli_pggb_ava.pif.gz",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"]
}
}
jbrowse add-track ecoli_pggb_ava.pif.gz \
--trackId ecoli_pggb_ava \
--name "pggb graph: all-vs-all synteny (wfmash)" \
--assemblyNames K12,Sakai,CFT073,NCTC86,IAI39 \
--adapterType MultiGenomeIndexedPAFAdapter \
--load copy
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": "ecoli_pggb_ava",
"name": "pggb graph: all-vs-all synteny (wfmash)",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"adapter": {
"type": "MultiGenomeIndexedPAFAdapter",
"uri": "ecoli_pggb_ava.pif.gz",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"]
}
}
Stack the five strains in a linear synteny view as the
all-vs-all tutorial
describes. The PanSN sample# prefix is how the adapter maps a record to its
strain.
wfmash merges each pair into a few dozen long segments where minimap2 leaves
several hundred, so the same minAlignmentLength cuts less here. It also maps
in both directions, so every pair is in the PAF twice and the ribbons draw twice
as opaque.
The projection from odgi untangle
odgi untangle
walks each query path and reports which stretch of the reference path it
traverses, so it states homology as the graph resolved it. Sequence that
collapsed into one set of nodes comes back as several query segments pointing at
the same reference span. -p asks for PAF:
printf 'K12#1#chr\n' > target.txt
printf 'Sakai#1#chr\nCFT073#1#chr\nNCTC86#1#chr\nIAI39#1#chr\n' > query.txt
in_pggb odgi untangle -i "/data/$og" \
-R /data/target.txt -Q /data/query.txt -m 1000 -j 0.5 -e 5000 -p -t "$(nproc)" \
> ecoli_pggb_untangle.paf
jbrowse make-pif ecoli_pggb_untangle.paf
-m merges runs shorter than it into the previous segment, since otherwise
every SNP node starts a new one, and -j keeps mappings at or above a jaccard.
untangle writes no CIGAR and states its identity in an id:f: tag, which a
synteny track reads on a record with no de:f:.
On a near-colinear bacterial pangenome untangle finds few boundaries, so -e
forces one every N bp of the sorted graph, which is what makes the figures below
readable. The cut is baked into the file, so leave it off on a graph with many
haplotypes. The
Minigraph-Cactus tutorial
builds the same projection with halSynteny.
Load it as its own SyntenyTrack:
{
"type": "SyntenyTrack",
"trackId": "ecoli_pggb_untangle",
"name": "pggb graph: synteny from the graph (odgi untangle)",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"adapter": {
"type": "MultiGenomeIndexedPAFAdapter",
"uri": "ecoli_pggb_untangle.pif.gz",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"]
}
}
jbrowse add-track ecoli_pggb_untangle.pif.gz \
--trackId ecoli_pggb_untangle \
--name "pggb graph: synteny from the graph (odgi untangle)" \
--assemblyNames K12,Sakai,CFT073,NCTC86,IAI39 \
--adapterType MultiGenomeIndexedPAFAdapter \
--load copy
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": "ecoli_pggb_untangle",
"name": "pggb graph: synteny from the graph (odgi untangle)",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"adapter": {
"type": "MultiGenomeIndexedPAFAdapter",
"uri": "ecoli_pggb_untangle.pif.gz",
"assemblyNames": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"]
}
}
untangle projects queries onto a target path, so every record has K12 on one side and a band between two non-reference rows has nothing to draw. Put the reference between the strains you want to compare.
The two files differ at a repeat. Find one by looking for a reference span that more than one segment of the same query lands on:
gzip -dc ecoli_pggb_untangle.pif.gz | awk -F'\t' 'substr($1,1,1)=="q"' \
| cut -f1,3,4,8,9 | sort -k4,4n
Two K12 spans come back, chr:3,941,447-3,944,255 and
chr:4,169,192-4,171,723, and Sakai, NCTC86 and IAI39 each reach both from two
places; CFT073 reaches neither twice. A pairwise PAF cannot say this, since a
collapsed repeat is either dropped or assigned to one copy.
A dotplot reads the same PIF. A stretch the strain traverses backwards descends, so every inversion is visible at once.
Untangle indexes every step of every path, so it is the slower of the two;
restrict -Q to the paths you need on a base-level graph.
One lane per strain, on the K12 axis
The same records drawn as a multi-row feature track put every strain on the reference at once, one row each, so orientation is read down a column. PAF column 5 is the strand, which the variant and MAF projections carry no field for.
untangle_to_bed.py projects the PAF onto the per-strain BED schema
build_minigraph_paths.sh
defines, so partitionField and the colors carry across. The
bubble-decomposition columns untangle does not report are left empty:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/untangle_to_bed.py
python3 untangle_to_bed.py ecoli_pggb_untangle.paf chr > ecoli_pggb_untangle_rows.bed
# the writer's header line is `#`-prefixed, and `jbrowse sort-bed` keeps every
# such line on top while sorting the rest, i.e. `sort -k1,1 -k2,2n` under LC_ALL=C
jbrowse sort-bed ecoli_pggb_untangle_rows.bed | bgzip > ecoli_pggb_untangle_rows.bed.gz
tabix -p bed ecoli_pggb_untangle_rows.bed.gz
Load the result as a FeatureTrack with a LinearMultiRowFeatureDisplay:
{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_untangle_rows",
"name": "pggb graph: untangle per strain (orientation, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "BedTabixAdapter",
"uri": "ecoli_pggb_untangle_rows.bed.gz"
},
"displays": [
{
"type": "LinearMultiRowFeatureDisplay",
"partitionField": "strain",
"rowOrder": ["Sakai", "CFT073", "NCTC86", "IAI39"],
"legend": [
{ "label": "Same orientation as K12", "color": "rgb(153,153,153)" },
{ "label": "Inverted", "color": "rgb(214,39,40)" }
]
}
]
}
jbrowse add-track-json '{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_untangle_rows",
"name": "pggb graph: untangle per strain (orientation, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "BedTabixAdapter",
"uri": "ecoli_pggb_untangle_rows.bed.gz"
},
"displays": [
{
"type": "LinearMultiRowFeatureDisplay",
"partitionField": "strain",
"rowOrder": ["Sakai", "CFT073", "NCTC86", "IAI39"],
"legend": [
{ "label": "Same orientation as K12", "color": "rgb(153,153,153)" },
{ "label": "Inverted", "color": "rgb(214,39,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": "FeatureTrack",
"trackId": "ecoli_pggb_untangle_rows",
"name": "pggb graph: untangle per strain (orientation, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "BedTabixAdapter",
"uri": "ecoli_pggb_untangle_rows.bed.gz"
},
"displays": [
{
"type": "LinearMultiRowFeatureDisplay",
"partitionField": "strain",
"rowOrder": ["Sakai", "CFT073", "NCTC86", "IAI39"],
"legend": [
{ "label": "Same orientation as K12", "color": "rgb(153,153,153)" },
{ "label": "Inverted", "color": "rgb(214,39,40)" }
]
}
]
}
partitionField gives each strain its own row, and the colors come from the
file's itemRgb. The white gaps are where a strain has no untangle segment on
that stretch of K12.
The box marks the same 594 kb arm in both figures. selfCov in the popup goes
above 1 where a segment lands on a reference span the same strain also lands on
elsewhere, so jexl:feature.selfCov>1 in Edit filters cuts the lane to the
collapsed repeats.
Pangenome variants projection
pggb -V writes a VCF of every variant the graph decomposes against the K12
path, genotyped across the other four strains. Its CHROM is the PanSN path
(K12#1#chr), so rename it to the assembly's refName (chr) with bcftools,
which ships in the pggb image:
printf 'K12#1#chr\tchr\n' > rename_chrs.tsv
in_pggb bash -c "bcftools annotate --rename-chrs /data/rename_chrs.tsv \
/data/pggb/*.smooth.final.K12.decomposed.vcf \
| bcftools sort -Oz -o /data/ecoli_pggb.vcf.gz && tabix -p vcf /data/ecoli_pggb.vcf.gz"
Load it as a VariantTrack on K12 with the
multi-sample display, one row per sample:
{
"type": "VariantTrack",
"trackId": "ecoli_pggb_variants",
"name": "pggb graph: pangenome variants (vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "VcfTabixAdapter",
"uri": "ecoli_pggb.vcf.gz"
},
"displays": [{ "type": "LinearMultiSampleVariantDisplay" }]
}
jbrowse add-track-json '{
"type": "VariantTrack",
"trackId": "ecoli_pggb_variants",
"name": "pggb graph: pangenome variants (vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "VcfTabixAdapter",
"uri": "ecoli_pggb.vcf.gz"
},
"displays": [{ "type": "LinearMultiSampleVariantDisplay" }]
}'
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": "VariantTrack",
"trackId": "ecoli_pggb_variants",
"name": "pggb graph: pangenome variants (vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "VcfTabixAdapter",
"uri": "ecoli_pggb.vcf.gz"
},
"displays": [{ "type": "LinearMultiSampleVariantDisplay" }]
}
Stacked over the MAF alignment (the figure under Whole-genome alignment (MAF) projection), the calls sit over the alignment they were decomposed from. The row orders differ: the variant lane follows the VCF's sample columns and the MAF lane the tree the track loads.
Why the reference path takes a length
A graph VCF is a snarl tree: vg deconstruct emits a record per snarl at
every level, each carrying LV (its level, 0 at the top) and PS (its
parent), so the file holds both a bubble and the variants nested inside it, and
the wide records paint over the SNPs underneath.
With a length, pggb runs vcfbub
-l 0 -a LEN piped into vcfwave and
writes *.decomposed.vcf beside the raw file. vcfbub pops any site whose
alleles run past LEN, emitting the nested sites in its place, and vcfwave
realigns what survives into primitive variants. On this graph nothing then
paints over the layer beneath it, so the track needs no display filter.
LEN is a cost knob too: vcfwave is dominated by the longest alleles, and
HPRC's own -a 100000 runs far longer here than -a 10000. Structural
variation that large reads better in the graph view or the per-strain path
track.
Keep the raw file too, through the same rename, as a second track:
in_pggb bash -c "bcftools annotate --rename-chrs /data/rename_chrs.tsv \
/data/pggb/*.smooth.final.K12.vcf \
| bcftools sort -Oz -o /data/ecoli_pggb_snarls.vcf.gz && tabix -p vcf /data/ecoli_pggb_snarls.vcf.gz"
LV/PS give the snarl tree, and AT states each allele as the segment ids it
traverses (AT=>2>4>5,>2>3>5), the ids the graph view labels its nodes with.
Filter on LV in Edit filters to pick one level. The coarse tier below is
built from the LV=0 records.
The multi-sample variant track guide covers the matrix versus per-position display, genotype coloring, and clustering.
Whole-genome alignment (MAF) projection
pggb -M writes the multiple alignment as a MAF, which JBrowse reads as a
Config guide: MAF track. Its blocks are smoothxg's POA blocks,
with two consequences.
Block order. pggb orders each block from its longest path, so the reference
row is not consistently the same genome. Re-root every block on K12, dropping
blocks that lack it, and rename the PanSN names to sample.chr:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/reroot_maf.py
# reroot_maf.py keeps K12-containing blocks, puts K12 first (+ strand), sorts by
# K12 position, and gives each K12 row in a repeat-collapsed block its own block
python3 reroot_maf.py pggb/*.smooth.maf ecoli_pggb.maf K12#1#chr
An index keys a block on its first row, so a repeat's second copy is only
queryable once it anchors a block of its own, which is why
reroot_maf.py
splits them.
Block padding. A smoothxg bug leaves block padding on some rows, which reads
as a phantom insertion at every POA block boundary.
pangenome/smoothxg#223 fixes
it upstream, and no published pggb image carries the fix yet, so reroot_maf.py
crops around it.
Then convert the MAF to the tabix-indexed BED the
MafTabixAdapter reads. The usual converter,
maf2bed, picks the reference row by
assembly name and would undo the split above; maf_to_bed.py takes row 0 as the
reference:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/maf_to_bed.py
python3 maf_to_bed.py ecoli_pggb.maf ecoli_pggb.maf.bed
bgzip ecoli_pggb.maf.bed
tabix -p bed ecoli_pggb.maf.bed.gz
For the row order,
odgi similarity
reports how much of the graph each pair of samples shares, and UPGMA over
1 - estimated.identity turns that into the Newick the track reads as
nhLocation:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/odgi_similarity_to_newick.py
in_pggb odgi similarity -i "/data/$og" -D '#' -p 1 > ecoli_pggb_similarity.tsv
python3 odgi_similarity_to_newick.py ecoli_pggb_similarity.tsv ecoli_pggb.nh
{
"type": "MafTrack",
"trackId": "ecoli_pggb_maf",
"name": "pggb graph: whole-genome alignment (MAF, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MafTabixAdapter",
"samples": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"nhLocation": { "uri": "ecoli_pggb.nh" },
"uri": "ecoli_pggb.maf.bed.gz"
}
}
jbrowse add-track-json '{
"type": "MafTrack",
"trackId": "ecoli_pggb_maf",
"name": "pggb graph: whole-genome alignment (MAF, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MafTabixAdapter",
"samples": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"nhLocation": { "uri": "ecoli_pggb.nh" },
"uri": "ecoli_pggb.maf.bed.gz"
}
}'
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": "MafTrack",
"trackId": "ecoli_pggb_maf",
"name": "pggb graph: whole-genome alignment (MAF, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MafTabixAdapter",
"samples": ["K12", "Sakai", "CFT073", "NCTC86", "IAI39"],
"nhLocation": { "uri": "ecoli_pggb.nh" },
"uri": "ecoli_pggb.maf.bed.gz"
}
}
samples names the rows, so a tree that fails to build leaves the track
working. A cell in the variant lane is colored by that strain's genotype:
- grey where the strain matches K12
- blue where it carries the alternate allele
- olive where the site is uncalled
- a numbered purple box where it carries an insertion, the number being how many bases the allele adds beyond K12
An insertion consumes no reference, so the record spans a single base and the marker carries its length. The alignment below draws the same insertions with the same marker; where the strains stop aligning to K12 at that coordinate the rows are left blank.
The MAF track guide covers the conservation band, per-row identity, and codon view.
Drag across the rows and the menu that opens on release lists each strain the selection covers:
- Open Sakai at the matching region: that strain, aligned
- Linear synteny view, K12 vs... opens the two as a linear synteny view
This works because the config loads the strains as assemblies under the names
the MAF uses; a
samples entry names the
assembly where the two differ.
Pangenome depth projection (core vs accessory)
odgi depth
counts how many paths traverse the graph under each reference base: near the
strain count over core sequence, toward 1 over K12-private sequence. Tile the
K12 path into windows, take each window's mean depth, rename the PanSN path to
chr, and convert to bigWig with
bedGraphToBigWig:
reflen=$(awk -v p="K12#1#chr" '$1 == p {print $2}' all.fa.gz.fai)
awk -v p="K12#1#chr" -v len="$reflen" -v w=500 \
'BEGIN { for (s = 0; s < len; s += w) { e = s + w; if (e > len) e = len
print p "\t" s "\t" e } }' > depth_windows.bed
# -b gives one row per window instead of per base, so the window size above is
# the resolution of the curve; the awk drops the PanSN prefix for the plain
# refName the K12 assembly uses
in_pggb odgi depth -i "/data/$og" -b /data/depth_windows.bed |
awk -v p="K12#1#chr" -v OFS='\t' '$1 == p && $4 + 0 == $4 { print "chr", $2, $3, $4 }' |
sort -k1,1 -k2,2n > ecoli_pggb_depth.bedgraph
printf 'chr\t%s\n' "$reflen" > chrom.sizes
bedGraphToBigWig ecoli_pggb_depth.bedgraph chrom.sizes ecoli_pggb_depth.bw
chrom.sizes is written by hand, since the .fai carries the PanSN path name.
Load it as a QuantitativeTrack on
K12:
{
"type": "QuantitativeTrack",
"trackId": "ecoli_pggb_depth",
"name": "pggb graph: pangenome depth (paths over K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "BigWigAdapter",
"uri": "ecoli_pggb_depth.bw"
}
}
jbrowse add-track ecoli_pggb_depth.bw \
--trackId ecoli_pggb_depth \
--name "pggb graph: pangenome depth (paths over K12)" \
--assemblyNames K12 \
--load copy
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": "ecoli_pggb_depth",
"name": "pggb graph: pangenome depth (paths over K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "BigWigAdapter",
"uri": "ecoli_pggb_depth.bw"
}
}
Zoomed out, the track is the pangenome's core/accessory landscape along K12:
- a plateau near the strain count
- spikes past it over the rRNA operons the graph collapses into one copy
- troughs at 1 over K12's private sequence, mostly cryptic prophages and IS elements
The depth lane is drawn at the end of this section, under the per-strain rows. Zoom into one trough and the gene lane names it, which is what the figure below does for the widest.
An unrelated isolate's long reads say the same thing without the graph: nanopore
reads from E. coli E146
(ENA DRR193901), a clinical
isolate that is not one of the five, mapped straight onto K12 with
minimap2 -ax map-ont. The pileup links supplementary segments, so a read split
at the prophage boundary is joined to its other half, and reads long enough to
cross the element carry it as a single labelled deletion.
odgi depth counts path steps, and the graph collapses the rRNA operons
into one copy every strain walks several times, so those windows read above the
strain count. The Minigraph-Cactus tutorial draws
both graphs' curves over one of those operons.
Per-strain presence
odgi pav
splits depth per strain: over the same windows it reports the fraction of each
window that strain's path traverses. Slice each strain's rows into its own
bigWig and load the set as one
MultiQuantitativeTrack:
in_pggb odgi pav -i "/data/$og" -b /data/depth_windows.bed > pav.tsv
# K12 omitted: it is present over its own windows by construction
for strain in Sakai CFT073 NCTC86 IAI39; do
# column 5 is the PanSN path, column 6 the presence fraction
awk -F'\t' -v OFS='\t' -v g="${strain}#1#chr" \
'$5 == g && $6 + 0 == $6 { print "chr", $2, $3, $6 }' pav.tsv |
sort -k1,1 -k2,2n > "pav_${strain}.bedgraph"
bedGraphToBigWig "pav_${strain}.bedgraph" chrom.sizes "ecoli_pggb_pav_${strain}.bw"
done
The output is one row per window per path (chrom start end name group pav),
where group is the PanSN path and pav the fraction.
{
"type": "MultiQuantitativeTrack",
"trackId": "ecoli_pggb_pav",
"name": "pggb graph: per-strain presence (odgi pav, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MultiWiggleAdapter",
"subadapters": [
{
"type": "BigWigAdapter",
"name": "Sakai",
"uri": "ecoli_pggb_pav_Sakai.bw"
},
{
"type": "BigWigAdapter",
"name": "CFT073",
"uri": "ecoli_pggb_pav_CFT073.bw"
},
{
"type": "BigWigAdapter",
"name": "NCTC86",
"uri": "ecoli_pggb_pav_NCTC86.bw"
},
{
"type": "BigWigAdapter",
"name": "IAI39",
"uri": "ecoli_pggb_pav_IAI39.bw"
}
]
}
}
jbrowse add-track-json '{
"type": "MultiQuantitativeTrack",
"trackId": "ecoli_pggb_pav",
"name": "pggb graph: per-strain presence (odgi pav, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MultiWiggleAdapter",
"subadapters": [
{
"type": "BigWigAdapter",
"name": "Sakai",
"uri": "ecoli_pggb_pav_Sakai.bw"
},
{
"type": "BigWigAdapter",
"name": "CFT073",
"uri": "ecoli_pggb_pav_CFT073.bw"
},
{
"type": "BigWigAdapter",
"name": "NCTC86",
"uri": "ecoli_pggb_pav_NCTC86.bw"
},
{
"type": "BigWigAdapter",
"name": "IAI39",
"uri": "ecoli_pggb_pav_IAI39.bw"
}
]
}
}'
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": "MultiQuantitativeTrack",
"trackId": "ecoli_pggb_pav",
"name": "pggb graph: per-strain presence (odgi pav, vs K12)",
"assemblyNames": ["K12"],
"adapter": {
"type": "MultiWiggleAdapter",
"subadapters": [
{
"type": "BigWigAdapter",
"name": "Sakai",
"uri": "ecoli_pggb_pav_Sakai.bw"
},
{
"type": "BigWigAdapter",
"name": "CFT073",
"uri": "ecoli_pggb_pav_CFT073.bw"
},
{
"type": "BigWigAdapter",
"name": "NCTC86",
"uri": "ecoli_pggb_pav_NCTC86.bw"
},
{
"type": "BigWigAdapter",
"name": "IAI39",
"uri": "ecoli_pggb_pav_IAI39.bw"
}
]
}
}
Under the aggregate curve, these rows say which strain each dip is missing. Across the shaded span K12 carries ybaL through the allantoin operon, one row goes white for its full width, and a second goes white over the rhsD Rhs element alone.
Compared to odgi viz
pggb rendered the graph in 1D with
odgi viz
(*.viz_*.png) and in 2D with
odgi layout
(*.lay.draw.png). The figure below is odgi viz re-run at a printable size.
One row per strain over a horizontal axis of graph node order; the brackets under the rows are the links. The JBrowse projections keep one row per strain and redraw everything on K12's coordinates:
- Depth is the raster's column coverage summed into one curve.
- Per-strain presence is its filled-vs-gap rows, windowed.
- The MAF track is those same rows at single-base resolution, colored by mismatch.
- The variant track is the points where the rows branch, one column each.
The
Minigraph-Cactus page
marks the same 100 kb of K12 on both axes. odgi layout's 2D drawing is
path-guided stochastic gradient descent over the whole graph; the graph view's
force-directed layout is Bandage's FMMM over one cut window, plus two anchored
layouts.
Opening the graph in the graph genome view
JBrowse can also draw the graph as a graph beside a linear view of the same window, through the graph genome view plugin. That guide covers the view and its layouts; this section covers getting a base-level graph in.
Installing the plugin
The plugin is beta and not in the plugin store
yet, so it loads by URL from a top-level plugins array in config.json (see
configuring plugins):
{
"plugins": [
{
"name": "GraphGenomeView",
"esmUrl": "https://jbrowse.org/demos/graphgenomeviewer/jbrowse-plugin-graphgenomeviewer.esm.js"
}
]
}
RgfaTabixAdapter ships in the same plugin, so the segment tracks below need it
too. On JBrowse Desktop, install it once from the
start screen at Global plugins... → Add custom plugin, putting that esmUrl
under Advanced options in ESM build URL.
Browsing the whole graph by locus
A plain GFA records no coordinates on its segments, but walking a P line in step
order gives every segment an interval on that path. Doing that once, offline,
and writing the two tabix-indexed BEDs RgfaTabixAdapter reads makes the whole
graph queryable by locus:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_pggb_tabix.sh
bash build_pggb_tabix.sh "$gfa" ecoli_pggb K12
It produces ecoli_pggb.segs.bed.gz and ecoli_pggb.links.bed.gz with their
indexes. The
graph view guide
covers the four choices that walk makes. Load the pair as one FeatureTrack
pointed at the shared prefix:
{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_segments",
"name": "pggb graph segments (whole graph, by locus)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displayDefaults": { "showLabels": "none" }
}
jbrowse add-track-json '{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_segments",
"name": "pggb graph segments (whole graph, by locus)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displayDefaults": { "showLabels": "none" }
}'
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": "FeatureTrack",
"trackId": "ecoli_pggb_segments",
"name": "pggb graph segments (whole graph, by locus)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displayDefaults": { "showLabels": "none" }
}
A segment's name is its GFA id, and pggb cuts one every ~17 bp, so showLabels
is off here and in the figures below.
The segments draw as an ordinary track on K12, and Track menu → Launch → Graph
genome view (this region) cuts a subgraph from the index with no odgi step.
Rubberbanding the ruler and picking Graph genome view (this selection) does
the same for a dragged window. With the
all-vs-all alignment open in the same view,
that Launch submenu carries Linear synteny view beside it. Each view
reaches the other again: a drag on any synteny row's scale bar raises the same
Launch menu anchored on that strain, Replace current view there
re-anchors the stack on it, the segments lane's track menu cuts the graph from
inside the stack, and the
graph's own menu
opens the strains as a stack.
The clip below takes that from the beginning: a K12 session carrying the plugin and its gene track, the block above pasted in through Open track..., and the graph cut from the window that leaves.
A node's drawn length is proportional to its sequence by default, so one long arm can swallow the rest of the drawing. Bubble spread → Compress lengths pulls the longest and shortest nodes towards the mean; use it whenever a cut spans kilobases and single bases at once.
One node per bubble, when the window is wider than the graph can draw
The index above draws one node per GFA segment, about 17 bp each, so the
drawable window is a kilobase or so. A coarse tier draws one node per bubble
instead, with the invariant reference between bubbles as backbone.
RgfaTabixAdapter reads it unchanged, since a collapsed bubble is a reference
span with an id and a rank. The bubble decomposition comes from pggb -V: the
LV=0 records of its snarl VCF are the top-level bubbles, and
snarls_to_bubble_bed.py
turns them into the bubble BED the tier builder reads:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/snarls_to_bubble_bed.py
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_bubble_tier.sh
python3 snarls_to_bubble_bed.py ecoli_pggb_snarls.vcf.gz ecoli_pggb.bubbles.bed
bash build_bubble_tier.sh ecoli_pggb.bubbles.bed ecoli_pggb.tier50 50
That is the raw snarl tree kept above,
since vcfbub pops exactly the top-level records a tier is built from.
(gfatools bubble reads rGFA SN/SO/SR tags, which a pggb graph lacks, so
it reports nothing here.)
The third argument is the threshold in bp of content: at 50, single-base alternatives are absorbed into the backbone and every indel is kept, taking the whole graph to about a thousand nodes:
{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_tier50",
"name": "pggb graph bubbles (coarse tier, one node per bubble)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb.tier50"
}
}
jbrowse add-track-json '{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_tier50",
"name": "pggb graph bubbles (coarse tier, one node per bubble)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb.tier50"
}
}'
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": "FeatureTrack",
"trackId": "ecoli_pggb_tier50",
"name": "pggb graph bubbles (coarse tier, one node per bubble)",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb.tier50"
}
}
The builder ranks an invariant stretch 0 and a bubble 1, so the reference-position ramp colors the stretches the strains agree on and paints charcoal on the sites they differ at.
Hover a node for the segments it collapsed, how many traversals cross it, and its shortest and longest allele. The two tiers are read together, the coarse one to find an event and the fine one to open it: a tier node's Open in K12 takes the linear view to the span it stands for, inside the kilobase the fine index draws at.
Switching Layout to Sample rows gives each strain its own row. On this
graph a row means carriage, since it names a path that walks the segment; on an
rGFA it means build order, from minigraph's SR.
Rows want a narrower window, a few hundred bp, since each segment is read
individually. A row's bar is drawn over the reference it replaces, never
over its own sequence length, so an insertion's length lives in the tooltip.
That is why CFT073's row carries one long bar labelled 7 kb deletion running
off the left edge: its segment is 75 bp on CFT073's own contig, and its two
links land on K12:1,004,667 inside the window and on K12:997,574 7.1 kb
upstream. pggb -V writes the same event as one record at chr:997,575
genotyped in CFT073 alone, and it is drawn again
below from CFT073's own coordinates.
In Sample rows the top row is the K12 backbone, and below it each strain's marks are the segments it takes instead, in the MAF's own row order.
The dropdown redraws the same nodes into either layout:
Who carries a segment
Clicking a node opens its details, which on a graph indexed this way include
carriedBy: every haplotype whose path walks that segment, recorded by
build_pggb_tabix.sh as an SM:Z: tag. contributingAssembly in the same
panel is the field an rGFA has to use, and there SR is build order.
Carriage as a linear lane
The same tag reaches the segments track as feature attributes: samples is the
haplotype list and carriers its length, which a color expression wants:
{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_carriage",
"name": "pggb graph: segment carriage",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displays": [
{
"type": "LinearBasicDisplay",
"displayId": "ecoli_pggb_carriage-LinearBasicDisplay",
"displayMode": "collapsed",
"showLabels": false,
"color": "jexl:feature.carriers==1?'#e31a1c':feature.carriers==2?'#fd8d3c':feature.carriers==3?'#feb24c':feature.carriers==4?'#fed976':feature.carriers==5?'#bdbdbd':'#eeeeee'",
"legend": [
{ "label": "All 5 strains (core)", "color": "#bdbdbd" },
{ "label": "4 strains", "color": "#fed976" },
{ "label": "3 strains", "color": "#feb24c" },
{ "label": "2 strains", "color": "#fd8d3c" },
{ "label": "1 strain (private)", "color": "#e31a1c" }
]
}
]
}
jbrowse add-track-json '{
"type": "FeatureTrack",
"trackId": "ecoli_pggb_carriage",
"name": "pggb graph: segment carriage",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displays": [
{
"type": "LinearBasicDisplay",
"displayId": "ecoli_pggb_carriage-LinearBasicDisplay",
"displayMode": "collapsed",
"showLabels": false,
"color": "jexl:feature.carriers==1?'\''#e31a1c'\'':feature.carriers==2?'\''#fd8d3c'\'':feature.carriers==3?'\''#feb24c'\'':feature.carriers==4?'\''#fed976'\'':feature.carriers==5?'\''#bdbdbd'\'':'\''#eeeeee'\''",
"legend": [
{ "label": "All 5 strains (core)", "color": "#bdbdbd" },
{ "label": "4 strains", "color": "#fed976" },
{ "label": "3 strains", "color": "#feb24c" },
{ "label": "2 strains", "color": "#fd8d3c" },
{ "label": "1 strain (private)", "color": "#e31a1c" }
]
}
]
}'
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": "FeatureTrack",
"trackId": "ecoli_pggb_carriage",
"name": "pggb graph: segment carriage",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "https://jbrowse.org/demos/ecoli_pangenome/ecoli_pggb"
},
"displays": [
{
"type": "LinearBasicDisplay",
"displayId": "ecoli_pggb_carriage-LinearBasicDisplay",
"displayMode": "collapsed",
"showLabels": false,
"color": "jexl:feature.carriers==1?'#e31a1c':feature.carriers==2?'#fd8d3c':feature.carriers==3?'#feb24c':feature.carriers==4?'#fed976':feature.carriers==5?'#bdbdbd':'#eeeeee'",
"legend": [
{ "label": "All 5 strains (core)", "color": "#bdbdbd" },
{ "label": "4 strains", "color": "#fed976" },
{ "label": "3 strains", "color": "#feb24c" },
{ "label": "2 strains", "color": "#fd8d3c" },
{ "label": "1 strain (private)", "color": "#e31a1c" }
]
}
]
}
The depth track answers the same question as a mean over windows, so an accessory stretch shorter than one window is averaged into its neighbours; the lane is one box per segment.
The last color in the chain is the fallback: an rGFA has no tag column, so
carriers is absent and the whole lane comes out in that color.
Opening a node in its own strain
A segment the reference never visits sits on its own carrier's coordinates,
so the graph can open the strain itself. Right-click the 75 bp CFT073 segment
and pick Open in CFT073: it opens CFT073 at 1,048,515 with CFT073's gene
track. The Launch cascade in the view menu is the whole-window version of
the same thing.
Seven K12 genes sit inside the span the two links bridge (the elf fimbrial operon elfA, elfD, elfC, elfG, and ycbU, ycbV, ycbF), with ssuE ending just before it and pyrD starting just after. CFT073 runs ssuE straight into pyrD.
The graph genome view guide covers the rest of that menu, including the synteny entry that opens every contributing strain at once.
Limits of the locus index
Three limits on browsing a base-level graph by locus:
- The index is offline. It is rebuilt whenever the graph changes.
- It grows with total sequence rather than with variation. A human pangenome
at base level is orders of magnitude past a bacterial one, so index a
community or a chromosome at a time (pggb itself partitions via
partition-before-pggb) and prefer the SV-resolution minigraph graph for whole-genome browsing, as the HPRC tutorial does. - The drawable window is small. At 17 bp per segment, 3 kb is a solid braid, and the view declines past its node budget.
A segment carried by several assemblies draws on one row, the first path that
walks it; the rest are in the node popup under
carriedBy.
The build script also runs minigraph -cxggs over
the same five strains and indexes its rGFA the same way, so the figure below
puts one locus through both graphs.
Both panes are colored by reference position over the same 28 kb. Browse the rGFA whole-genome, and open the pggb graph where you want every base.
A window as a file
With no index, Add → Graph genome view takes a GFA by file or URL, for a graph too large to index or a window someone hands you. Three odgi commands cut one:
extract -Etakes every node between the first and last in the rangesort -Ocompacts the node idsview -gwrites GFA
-E is the aggressive option; -c/-d expand by a bounded number of steps or
bp instead, which is what the view's own Graph context setting does when it
cuts from an index:
in_pggb bash -c "odgi extract -i /data/$og -r K12#1#chr:1004500-1004900 -E -o - \
| odgi sort -i - -o - -O \
| odgi view -i - -g" > ecoli_pggb_subgraph.gfa
Nothing in a plain GFA marks one path as the reference, so pick it under View
menu → Settings → Reference path. odgi extract writes the window into the
path name (K12#1#chr:1004500-1004961), which is where the offsets come from.
The same walk outside the browser puts those nodes on a linear track:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/gfa_nodes_to_bed.py
python3 gfa_nodes_to_bed.py ecoli_pggb_subgraph.gfa K12#1#chr chr \
| sort -k1,1 -k2,2n | bgzip > ecoli_pggb_subgraph_nodes.bed.gz
tabix -p bed ecoli_pggb_subgraph_nodes.bed.gz
The BED's itemRgb is the view's own viridis Depth ramp, so the track needs
no color configuration. Nodes the reference path never visits have no K12
position, so they are absent from the linear track.
A cut graph has ends. The 93 bp node at the green-to-yellow junction, ringed in
the figure below, is where CFT073 rejoins after
the same deletion drawn above, and its
second link falls 7 kb outside the window, so it draws with one end open.
Widening the window until it closes returns thousands of segments where this one
returns 48; -c 1 fetches the far anchor without everything between.
The same event is four nodes in the minigraph graph of these strains, where a structural graph spends one segment on the 7 kb K12 stretch:
tabix https://jbrowse.org/demos/ecoli_pangenome/ecoli_minigraph.links.bed.gz \
'K12#1#chr:997000-1005000'
s378 → s379 → s380 is K12 through the deletion and s378 → s2025 → s380 is
CFT073 around it, where s2025 is this same CFT073 sequence.
-d is the answer at a collapsed repeat, where -E walks out of the window to
every copy: at the 16S rRNA gene rrsB it returns tens of thousands of segments
for a 500 bp cut, where -d 500 returns six.
in_pggb bash -c "odgi extract -i /data/$og -r K12#1#chr:4166800-4167300 -d 500 -o - \
| odgi sort -i - -o - -O \
| odgi view -i - -g" > ecoli_pggb_rrna.gfa
odgi paths -L on that cut lists nine path intervals over those six segments,
two copies each in Sakai, CFT073, NCTC86 and IAI39 and one in K12: the collapse
the depth curve reads as a spike and
untangle draws in coordinate space.
Drawing the haplotype paths
A P line is a path, the ordered list of segments one strain takes through the graph, and a W line is the same thing in GFA 1.1's walk syntax. View menu → Settings → Draw paths splits every node and connector lengthwise into one lane per path, and a strain that does not walk a node leaves its lane empty there. Set Color to Grey first, so the only colors are the paths.
The setting needs a graph with P or W records. An rGFA has neither, nor does a subgraph cut from the tabix index, which rebuilds segments and links only. The file route keeps them: cut the IS5 bubble the coarse tier arrows as a file.
in_pggb bash -c "odgi extract -i /data/$og -r K12#1#chr:1299400-1300800 -E -o - \
| odgi sort -i - -o - -O \
| odgi view -i - -g" > ecoli_pggb_is5.gfa
The figure keeps the same interval in K12 coordinates above the graph. The gene lane names the element (insH21, the IS5 transposase), and the broken line in the drawing is the deletion edge.
Reproduce it end to end
build_ecoli_pangenome_graph.sh
runs everything above in one shot, fetching the helper scripts it needs beside
itself:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_ecoli_pangenome_graph.sh
bash build_ecoli_pangenome_graph.sh # builds ./ecoli_pangenome_graph_build/jbrowse2
npx --yes serve ecoli_pangenome_graph_build/jbrowse2
In one run it:
- downloads the RefSeq genomes
- runs pggb
- converts the wfmash PAF,
odgi untangle, both VCF tiers, the MAF,odgi similarity,odgi depthandodgi pavinto the projections above - downloads JBrowse
- writes a
config.jsonwith the assemblies, per-strain gene tracks, the graph-derived tracks, and a default session - writes the
odgi vizraster and every cut GFA this page opens as a file:ecoli_pggb_subgraph.gfa,ecoli_pggb_is5.gfaandecoli_pggb_rrna.gfaout of odgi, thenecoli_rgfa_slice.gfa,ecoli_paa_subgraph.gfaand the rGFA tabix indexes behind the segments track out of the cactus image, which is where minigraph and gfatools live
The config.json declares the graph genome view plugin. The script needs the
tools under Prerequisites and picks its container runtime off
PATH, docker first; force one with CONTAINER=singularity.
JBrowse Desktop opens the folder's config.json by
path, so npx serve is only for the web build.
Adding genomes to the strain table at the top of the script is the only edit an
expanded pangenome needs. wfmash mapping scales with the square of the genome
count, and odgi untangle indexes every step of every path.
See also
- Graph genome view
- Pangenome (Minigraph-Cactus)
- Pangenome (HPRC)
- Synteny visualization (all-vs-all minimap2)
- Synteny from gene symbols (44 E. coli genomes)
- User guide: MAF track
- Multi-sample variant display
- PIF (Pairwise Indexed Format)
- jbrowse-anywidget
- JBrowseR
- pggb
- odgi
Feedback on this tutorial is welcome: contact us.