Pangenome (hosting your own graph)
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.11. Desktop beta builds are coming soon.
To let people browse a pangenome graph in JBrowse, from a whole chromosome down to single nodes, the graph has to be cut into small indexed files that JBrowse reads one window at a time. One command writes those files and the config that puts them on tracks. We run it on HPRC release 2, and:
- load the plugin and run the command
- open the track as a graph and as a lane of segments
- check the index against the graph
- add two optional layers: the haplotypes on each segment, and the walk of each haplotype
The graph view is a beta plugin. We welcome your feedback.
Prerequisites
- the GraphGenomeView plugin
- htslib (
bgzip,tabix),bcftools,python3,sort gfatoolsand GNU awk, for an rGFAminigraph, for each assembly's path through the graphvg1.69.0+,gbz-baseandgbz-haplotype-index, for haplotype walks
Where the data comes from
HPRC release 2's Minigraph-Cactus graph, which every HPRC page on this site reads.
- the SV-resolution rGFA: s3-us-west-2.amazonaws.com/…/hprc-v2.1-mc-grch38.sv.gfa.gzhttps://s3-us-west-2.amazonaws.com/human-pangenomics/pangenomes/freeze/release2/minigraph-cactus/v2.1/hprc-v2.1-mc-grch38/hprc-v2.1-mc-grch38.sv.gfa.gz
- the base-level GFA: s3-us-west-2.amazonaws.com/…/hprc-v2.1-mc-grch38.gfa.gzhttps://s3-us-west-2.amazonaws.com/human-pangenomics/pangenomes/freeze/release2/minigraph-cactus/v2.1/hprc-v2.1-mc-grch38/hprc-v2.1-mc-grch38.gfa.gz
- the same graph in vg's format: s3-us-west-2.amazonaws.com/…/hprc-v2.1-mc-grch38.gbzhttps://s3-us-west-2.amazonaws.com/human-pangenomics/pangenomes/freeze/release2/minigraph-cactus/v2.1/hprc-v2.1-mc-grch38/hprc-v2.1-mc-grch38.gbz
- the finished files, for comparison: jbrowse.org/…/README.txthttps://jbrowse.org/demos/hprc/README.txt
- the config the HPRC page opens: jbrowse.org/…/config.jsonhttps://jbrowse.org/pangenome/hprc-grch38/config.json
The GraphGenomeView plugin
GraphGenomeView loads by URL, from a plugins array at the top of config.json
(configuring plugins). The config the command
writes holds this entry:
{
"plugins": [
{
"name": "GraphGenomeView",
"esmUrl": "https://jbrowse.org/plugins/jbrowse-plugin-graphgenomeviewer/latest/dist/jbrowse-plugin-graphgenomeviewer.esm.js"
}
]
}On JBrowse Desktop v5.0.0-beta.1 or later, install
it once at Global plugins... → Add custom plugin: open Advanced options,
paste that esmUrl into ESM build URL and leave the rest empty.
Indexing a graph with build_pangenome_graph.sh
Fetch the script:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_pangenome_graph.shAn rGFA, from minigraph or the minigraph stage of Minigraph-Cactus, needs an output prefix and the assembly name your config uses:
bash build_pangenome_graph.sh graph.rgfa.gz out --assembly hg38A plain GFA, from pggb, odgi, vg or base-level Minigraph-Cactus, also needs
the backbone sample and its bubbles. vg deconstruct writes the snarl VCF the
bubbles come from, one record per top-level snarl against the reference path
(pggb -V writes the same file). The VCF's CHROM must be the assembly's
refName, so rename the PanSN path as the
pggb tutorial
does:
# -p: the reference path to decompose against
vg deconstruct -p K12#1#chr graph.gbz > graph.snarls.vcf
printf 'K12#1#chr\tchr\n' > rename_chrs.tsv
bcftools annotate --rename-chrs rename_chrs.tsv graph.snarls.vcf \
| bcftools sort -Oz -o graph.snarls.vcf.gzbash build_pangenome_graph.sh graph.gfa out --reference K12 --assembly K12 --snarls graph.snarls.vcf.gzThe rGFA route runs GNU awk, which is brew install gawk on macOS with gnubin
first on PATH.1 At human-chromosome scale, index the SV-resolution rGFA;
a pggb graph's index does not finish there. An assembly graph from SPAdes or
Flye has no reference to index against; open it in Bandage.
The command writes these files beside the prefix:
| file | what it holds |
|---|---|
.segs.bed.gz | one row per segment, at its reference coordinate |
.links.bed.gz | one row per link per endpoint, both ends stated in full |
.bubbles.bed.gz | where haplotypes diverge and rejoin, with each bubble's shortest and longest allele |
.tier10000.segs.bed.gz | one node per bubble, so a whole chromosome draws |
.alleles.bed.gz | one row per allele, with a CIGAR that states its size |
.config.json | the tracks below, with the plugin entry |
The bubble tier
The tier draws each bubble as one node on the reference backbone, and folds
bubbles under its threshold into the backbone. A bubble's size for that test is
the larger of its reference span and its longest allele. The threshold is in the
file name, 10,000 bp by default; --tier sets it, and a pggb graph defaults to
50, since most of its bubbles are single bases.
The graph track
The config's first track is the graph. uri is the prefix, and coarse names
the tier the track draws past aboveBpPerPx bp per pixel. assemblyNameToPanSN
maps your assembly name to the graph's PanSN sample, and the command writes it
only when the two differ.
Goes in the tracks array of config.json. See Tracks.
{
"type": "GraphTrack",
"trackId": "hprc_graph",
"name": "hprc graph",
"assemblyNames": ["hg38"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "hprc",
"assemblyNameToPanSN": { "hg38": "GRCh38" },
"coarse": { "uri": "hprc.tier10000", "aboveBpPerPx": 1014 }
},
"displayDefaults": { "showLabels": "none" },
"displays": [
{ "type": "LinearGraphDisplay" },
{ "type": "LinearBasicDisplay" }
]
}jbrowse add-track-json '{
"type": "GraphTrack",
"trackId": "hprc_graph",
"name": "hprc graph",
"assemblyNames": ["hg38"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "hprc",
"assemblyNameToPanSN": { "hg38": "GRCh38" },
"coarse": { "uri": "hprc.tier10000", "aboveBpPerPx": 1014 }
},
"displayDefaults": { "showLabels": "none" },
"displays": [
{ "type": "LinearGraphDisplay" },
{ "type": "LinearBasicDisplay" }
]
}'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": "GraphTrack",
"trackId": "hprc_graph",
"name": "hprc graph",
"assemblyNames": ["hg38"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "hprc",
"assemblyNameToPanSN": { "hg38": "GRCh38" },
"coarse": { "uri": "hprc.tier10000", "aboveBpPerPx": 1014 }
},
"displayDefaults": { "showLabels": "none" },
"displays": [
{ "type": "LinearGraphDisplay" },
{ "type": "LinearBasicDisplay" }
]
}hprc, hprc.tier10000 are relative to a config.json. Replace each with its URL or its path on this computer.
Turned on, the track draws as a graph. Display types → Feature display in its track menu draws the same segments as a lane.
The command writes out.config.json. Merge its plugins and tracks entries
into your own config.
The other three tracks in the config draw the bubbles as a lane and as a curve, and the allele inventory as an alignments track. Turn them on and switch the graph back with Display types → Graph.
A node's right-click menu offers Open in the haplotype named in its rGFA id,
such as NA20809#2#CM094351.1, when the session holds an assembly named or
aliased sample#haplotype, here NA20809#2.
The HPRC tutorial
takes that route.
Checking the index against the graph
Query a locus out of the index, by the graph's PanSN name for the reference contig:
tabix hprc.segs.bed.gz 'GRCh38#0#chr1:103,690,000-103,700,000' | head -3Columns one to three are the contig and span, four is the segment id, and five is its rank. Ask the graph about one of those segments:
gfatools view -l s12829 -r 0 hprc-v2.1-mc-grch38.sv.gfa.gzThe S-line's SN and SO tags match the row's first two columns, and SR
matches the fifth.
- An empty result over a tiled reference means the query used the wrong
name:
chr1finds nothing whereGRCh38#0#chr1finds every segment. - Backbone rows with no alleles mark a place where the graph collapsed, which minigraph does to near-identical segmental duplications.
Which haplotypes walk each segment
The rank in an rGFA is build order, so it names the first assembly a segment came from. The haplotypes whose paths walk each segment come from one of two sources.
With the assemblies, map each one back through the graph, reference first, and read the path it takes:
# --call: the path this sample takes through every bubble, one line per
# `gfatools bubble` line, in the same order for every sample
# -xasm: the assembly-to-graph preset
# -c: base-level alignment, which --call reads
minigraph -cxasm --call -t 8 graph.rgfa.gz sample.fa > sample.call.bedbuild_minigraph_paths.sh
runs that per assembly and joins the output into one tabix-indexed row per
bubble per sample, drawn as one lane per haplotype.
With a plain GFA, the command records the haplotypes whose paths visit each
segment as an SM:Z: tag while it walks the paths. The node panel lists them as
samples, and a track reads them as feature.samples and their count as
feature.sampleCount. Color by... → Attribute... with sampleCount gives
each count a separate colour. Past a handful of haplotypes a ramp reads better;
Edit plot... in the same dialog takes one, here red for a segment on one
haplotype to grey for a segment on most:
Goes in the tracks array of config.json. See Tracks.
{
"type": "FeatureTrack",
"trackId": "graph_haplotypes_per_segment",
"name": "graph: haplotypes per segment",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "graph"
},
"displayDefaults": {
"color": {
"field": "sampleCount",
"scale": "linear",
"domainMin": 1,
"range": ["#e31a1c", "#bdbdbd"],
"title": "Haplotypes"
}
}
}jbrowse add-track-json '{
"type": "FeatureTrack",
"trackId": "graph_haplotypes_per_segment",
"name": "graph: haplotypes per segment",
"assemblyNames": ["K12"],
"adapter": {
"type": "RgfaTabixAdapter",
"uri": "graph"
},
"displayDefaults": {
"color": {
"field": "sampleCount",
"scale": "linear",
"domainMin": 1,
"range": ["#e31a1c", "#bdbdbd"],
"title": "Haplotypes"
}
}
}'In JBrowse Desktop, or in any running JBrowse Web session, open a view on this track’s assembly, then File → Open track..., choose Add pangenome graph track, and fill in:
- File type:
rGFA segments (tabix BED pair) - Path:
graph.segs.bed.gz - Track name:
graph: haplotypes per segment - Assembly:
K12
graph is relative to a config.json. Replace it with its URL or its path on this computer.
The E. coli pggb tutorial draws the count over an IS5 insertion.
The count is per haplotype (HG002.1), so a diploid sample's two copies count
separately.
The HPRC tutorial
reads HPRC's phased VCF, which lists every haplotype's allele at every bubble.
Haplotype walks: a gbz-base database
A .gbz is vg's indexed form of a graph, with one walk per haplotype. The
browser reads it as a gbz-base database, the graph in SQLite, which three
commands build.
Build the distance-index chains. vg 1.69.0 or newer reads them out of a distance
index, and a top-level one (vg index --no-nested-distance) is enough:
vg chains graph.gbz graph.dist > graph.chainsBuild the database, one row per node and per path, plus the chains. Without
--chains, a window comes back as the reference walk alone.
gbz-base construct --chains graph.chains graph.gbzName the haplotypes. gbz-base reports the walks in a subgraph as unknown#1,
unknown#2, and gbz-haplotype-index writes their names to a companion file.
It reads the database beside the GBZ to check that the two match:
# --interval: bp between recorded GBWT positions per path; denser is bigger
# and faster
# --anchor-spacing: bp between anchor nodes on the reference path, so a window
# walks only the chosen lanes
gbz-haplotype-index --interval 16384 --anchor-spacing 131072 \
graph.gbz graph.gbz.db graph.haplotype-index.dbcargo install gbz-base and cargo install gbz-haplotype-index install the two
tools. The browser reads only the format 3 companion that gbz-haplotype-index
0.3.0 and later writes.2
Serve the database and the companion from URLs that answer range requests, and
point the track's uri and haplotypeIndexLocation at them. Each haplotype in
assemblyNames is an assembly whose aliases include its sample#haplotype
name, as
the HPRC tutorial
declares one; assemblyNameToPanSN covers the reference, which has none:
Goes in the tracks array of config.json. See Tracks.
{
"type": "GraphTrack",
"trackId": "my_graph_lanes",
"name": "My graph: haplotypes vs the reference, read from the graph",
"assemblyNames": ["hg38", "HG00097.1", "HG00099.1"],
"adapter": {
"type": "GbzBaseSyntenyAdapter",
"uri": "https://example.com/graphs/my_graph.gbz.db",
"haplotypeIndexLocation": {
"uri": "https://example.com/graphs/my_graph.haplotype-index.db"
},
"assemblyNames": ["hg38"],
"assemblyNameToPanSN": { "hg38": "GRCh38#0" },
"context": 1000,
"nodeLimit": 50000
},
"displays": [
{ "type": "MultiWaySyntenyDisplay", "height": 600 },
{ "type": "LinearGraphDisplay" }
]
}jbrowse add-track-json '{
"type": "GraphTrack",
"trackId": "my_graph_lanes",
"name": "My graph: haplotypes vs the reference, read from the graph",
"assemblyNames": ["hg38", "HG00097.1", "HG00099.1"],
"adapter": {
"type": "GbzBaseSyntenyAdapter",
"uri": "https://example.com/graphs/my_graph.gbz.db",
"haplotypeIndexLocation": {
"uri": "https://example.com/graphs/my_graph.haplotype-index.db"
},
"assemblyNames": ["hg38"],
"assemblyNameToPanSN": { "hg38": "GRCh38#0" },
"context": 1000,
"nodeLimit": 50000
},
"displays": [
{ "type": "MultiWaySyntenyDisplay", "height": 600 },
{ "type": "LinearGraphDisplay" }
]
}'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": "GraphTrack",
"trackId": "my_graph_lanes",
"name": "My graph: haplotypes vs the reference, read from the graph",
"assemblyNames": ["hg38", "HG00097.1", "HG00099.1"],
"adapter": {
"type": "GbzBaseSyntenyAdapter",
"uri": "https://example.com/graphs/my_graph.gbz.db",
"haplotypeIndexLocation": {
"uri": "https://example.com/graphs/my_graph.haplotype-index.db"
},
"assemblyNames": ["hg38"],
"assemblyNameToPanSN": { "hg38": "GRCh38#0" },
"context": 1000,
"nodeLimit": 50000
},
"displays": [
{ "type": "MultiWaySyntenyDisplay", "height": 600 },
{ "type": "LinearGraphDisplay" }
]
}The adapter rejects a companion built from a graph with a different path count.
Past nodeLimit nodes, or 5 Mb for the Graph display, both displays ask the
reader to zoom in.
Haplotypes against each other draws
the lanes this track produces.
Reproduce it end to end
The one command builds the graph track, the bubbles, the tier and the allele inventory, with the tools under Prerequisites. It:
- places every segment on a genome. An rGFA states each segment's sequence and offset in its own tags. For a plain GFA the command walks the backbone's paths first, so every segment they visit lands on the reference, and places each remaining segment on the first other haplotype that walks it
- finds the bubbles, with
gfatools bubbleon an rGFA or from the snarl VCF on a plain GFA, and builds the tier from them - reads each allele out of the links, following it from where it leaves the backbone to where it rejoins. The reference between those two points and the sequence the allele walks give the CIGAR its size
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_pangenome_graph.sh
bash build_pangenome_graph.sh hprc-v2.1-mc-grch38.sv.gfa.gz hprc --assembly hg38build_pangenome_graph.sh
fetches and runs
build_rgfa_tabix.sh
or
build_pggb_tabix.sh,
then
build_bubble_tier.sh
and
build_rgfa_alleles.sh,
each runnable alone. HPRC publishes the gbz-base database itself, so a separate
script builds only the
haplotype-walk companion, from the 5.5
GB .gbz and the 10 GB database:
curl -fO https://raw.githubusercontent.com/GMOD/jbrowse-components/main/scripts/build_hprc_gbz_index.sh
bash build_hprc_gbz_index.sh outSee also
- Pangenome (HPRC) part 1: the graph's alleles and the haplotypes that have them
- Pangenome (HPRC) part 2: haplotypes against each other
- Pangenome (pggb)
- Pangenome (Minigraph-Cactus)
- Graph genome view
External links
- Li H.
The rGFA format and
gfatools: the
SN/SO/SRtags and the bubble calls. - gbz-base: a GBZ as a SQLite database, range-requested per window.
Citations
- HPRC release 2, the worked example here.
Notes
-
BSD awk, the macOS default, takes hours on a large table where GNU awk takes minutes. ↩
-
Over HPRC's 464 haplotypes the companion is 5.1 GB, 0.47 GB of it the overview, built in 35 minutes on 22 threads of a 125 GB machine. On a 16-thread Intel Mac the build aborts inside libmalloc's nano zone;
MallocNanoZone=0or--threads 8avoids it. ↩
Feedback on this tutorial is welcome: contact us.