BAM Read Alignments¶
This example shows an IGV-like BAM alignment view built from GenomeSpy grammar building blocks. It combines depth coverage, mismatch and insertion support, read pileup, read direction, CIGAR operation overlays, per-read mismatch bases, and zoom-dependent lazy loading.
{
"description": [
"BAM read alignment example using a downsampled GIAB HG002 chr20 slice.",
"Shows an IGV-like BAM alignment view with depth coverage, insertion and mismatch summaries, directional read pileups, CIGAR overlays, and base-quality filtering."
],
"assembly": "hg38",
"scales": {
"x": {
"domain": [
{ "chrom": "chr20", "pos": 10031817 },
{ "chrom": "chr20", "pos": 10031936 }
]
}
},
"params": [
{
"name": "minMapq",
"value": 0,
"bind": {
"input": "range",
"min": 0,
"max": 60,
"step": 1,
"debounce": 100
}
},
{
"name": "minBaseQuality",
"value": 0,
"bind": {
"input": "range",
"min": 0,
"max": 40,
"step": 1,
"debounce": 100
}
},
{
"name": "windowSize",
"value": 15000
}
],
"data": {
"lazy": {
"type": "bam",
"url": "https://data.genomespy.app/sample-data/NIST-HG002/HG002.GRCh38.chr20_9950000_10100000.downsample33pct.bam",
"windowSize": { "expr": "windowSize" }
}
},
"transform": [
{
"type": "filter",
"expr": "datum.mapq == null || datum.mapq >= minMapq"
},
{
"type": "formula",
"expr": "datum.mapq == null ? 0 : datum.mapq",
"as": "_mapqOrZero"
},
{ "type": "pileup", "start": "start", "end": "end", "as": "_lane" }
],
"resolve": { "axis": { "x": "shared" } },
"spacing": 5,
"vconcat": [
{
"name": "coverage",
"title": {
"text": "Depth and mismatch support",
"style": "overlay-title",
"fontSize": 11
},
"height": 40,
"resolve": { "scale": { "color": "independent" } },
"layer": [
{
"name": "depth",
"title": "Depth",
"transform": [
{ "type": "flattenCigar", "copyFields": ["chrom"] },
{ "type": "filter", "expr": "datum.cigarType == 'aligned'" },
{
"type": "collect",
"sort": { "field": ["chrom", "cigarStart"] }
},
{
"type": "coverage",
"chrom": "chrom",
"start": "cigarStart",
"end": "cigarEnd",
"as": "coverage",
"asStart": "start",
"asEnd": "end"
}
],
"mark": "rect",
"encoding": {
"x": {
"chrom": "chrom",
"pos": "start",
"type": "locus",
"band": 0
},
"x2": { "chrom": "chrom", "pos": "end", "band": 0 },
"y": {
"field": "coverage",
"type": "quantitative",
"axis": { "tickCount": 2, "title": "Depth" }
},
"color": { "value": "#d0d0d0" }
}
},
{
"name": "mismatch-summary",
"title": "Mismatch support",
"transform": [
{ "type": "filter", "expr": "datum.md != null" },
{ "type": "alignmentMismatches", "copyFields": ["chrom"] },
{
"type": "filter",
"expr": "datum.baseQuality == null || datum.baseQuality >= minBaseQuality"
},
{
"type": "aggregate",
"groupby": ["chrom", "mismatchStart", "base"]
},
{
"type": "stack",
"field": "count",
"groupby": ["chrom", "mismatchStart"],
"sort": { "field": "base", "order": "ascending" },
"as": ["mismatchCount0", "mismatchCount1"]
},
{
"type": "formula",
"expr": "datum.mismatchStart + 1",
"as": "mismatchEnd"
}
],
"mark": "rect",
"encoding": {
"x": {
"chrom": "chrom",
"pos": "mismatchStart",
"type": "locus",
"band": 0
},
"x2": { "chrom": "chrom", "pos": "mismatchEnd", "band": 0 },
"y": { "field": "mismatchCount0", "type": "quantitative" },
"y2": { "field": "mismatchCount1" },
"color": {
"field": "base",
"type": "nominal",
"scale": {
"domain": ["A", "C", "T", "G", "N"],
"range": ["#4FBF45", "#4D96E8", "#E85F78", "#E8B322", "#BDBDBD"]
},
"legend": null
}
}
},
{
"name": "insertion-summary",
"title": "Insertion support",
"transform": [
{ "type": "flattenCigar", "copyFields": ["chrom"] },
{ "type": "filter", "expr": "datum.cigarType == 'insertion'" },
{
"type": "aggregate",
"groupby": ["chrom", "cigarStart"]
}
],
"mark": {
"type": "rule",
"color": "black",
"size": 1
},
"encoding": {
"x": {
"chrom": "chrom",
"pos": "cigarStart",
"type": "locus",
"band": 0
},
"y": { "datum": 0, "type": "quantitative" },
"y2": { "field": "count" }
}
}
]
},
{
"name": "read-alignments",
"title": { "text": "Read alignments", "orient": "none" },
"height": { "step": { "expr": "laneHeight" } },
"viewportHeight": "container",
"params": [
{
"name": "laneHeight",
"value": 12,
"bind": { "input": "range", "min": 1, "max": 30, "step": 1 },
"transition": { "type": "lerp", "halfLife": 30, "epsilon": 0.02 }
}
],
"resolve": {
"scale": { "color": "independent", "opacity": "independent" }
},
"layer": [
{
"name": "read-layers",
"title": "Read alignments",
"resolve": {
"scale": { "opacity": "independent" }
},
"encoding": {
"y": {
"field": "_lane",
"type": "index",
"scale": {
"padding": 0.25,
"reverse": true,
"zoom": false
}
}
},
"layer": [
{
"name": "read-backbone",
"title": "Read alignment",
"mark": {
"type": "arrow",
"style": "arrow-block",
"minStemLength": 10,
"fill": "#e0e0e0",
"stroke": "#c4c4c4",
"strokeWidth": { "expr": "linearstep(3, 8, laneHeight)" }
},
"encoding": {
"x": {
"chrom": "chrom",
"pos": "start",
"type": "locus",
"band": 0
},
"x2": { "chrom": "chrom", "pos": "end", "band": 0 },
"direction": {
"field": "strand",
"type": "nominal",
"scale": {
"domain": ["+", "-"],
"range": ["forward", "reverse"]
}
},
"opacity": {
"field": "_mapqOrZero",
"type": "quantitative",
"scale": { "domain": [0, 60], "range": [0.25, 1] },
"legend": {
"title": "Mapping quality",
"values": [0, 20, 40, 60]
}
},
"tooltip": [
{ "field": "name", "title": "Read" },
{ "field": "strand", "title": "Strand" },
{ "field": "isPaired", "title": "Paired" },
{ "field": "isProperPair", "title": "Proper pair" },
{ "field": "isDuplicate", "title": "Duplicate" },
{ "field": "isQcFail", "title": "QC fail" },
{ "field": "isSecondary", "title": "Secondary" },
{ "field": "isSupplementary", "title": "Supplementary" },
{ "field": "mapq", "title": "Mapping quality" },
{ "field": "cigar", "title": "CIGAR" }
]
}
},
{
"name": "cigar-overlays",
"title": "CIGAR operation",
"transform": [
{
"type": "formula",
"expr": "datum.seq",
"as": "_seq"
},
{
"type": "flattenCigar",
"copyFields": ["chrom", "_lane", "name", "cigar", "_seq"]
}
],
"encoding": {
"x": {
"chrom": "chrom",
"pos": "cigarStart",
"type": "locus",
"band": 0
},
"x2": { "chrom": "chrom", "pos": "cigarEnd", "band": 0 }
},
"layer": [
{
"name": "deletions",
"title": "Deletion",
"transform": [
{
"type": "filter",
"expr": "datum.cigarType == 'deletion'"
}
],
"layer": [
{
"title": "Deletion",
"mark": {
"type": "rect",
"color": "white",
"minWidth": 1
}
},
{
"title": "Deletion",
"mark": {
"type": "rule",
"color": "#222",
"minLength": 1
}
}
]
},
{
"name": "skips",
"title": "Skipped region",
"transform": [
{ "type": "filter", "expr": "datum.cigarType == 'skip'" }
],
"mark": {
"type": "rule",
"color": "#6b6b6b",
"strokeDash": [2, 2],
"minLength": 1
}
},
{
"name": "insertions",
"title": "Insertion",
"transform": [
{
"type": "filter",
"expr": "datum.cigarType == 'insertion'"
}
],
"encoding": {
"x2": null,
"tooltip": [
{
"expr": "slice(datum._seq, datum.readStart, datum.readEnd)",
"title": "Inserted sequence"
}
]
},
"mark": {
"type": "text",
"text": "I",
"color": "black",
"size": { "expr": "laneHeight * 0.90" },
"font": "Radley"
}
},
{
"name": "soft-clips",
"title": "Soft-clipped bases",
"transform": [
{
"type": "filter",
"expr": "datum.cigarType == 'softClip'"
}
],
"encoding": { "x2": null },
"mark": {
"type": "text",
"text": "S",
"color": "#555",
"size": { "expr": "laneHeight * 0.90" },
"font": "Radley"
}
}
]
},
{
"name": "mismatches",
"title": "Mismatch",
"transform": [
{ "type": "filter", "expr": "datum.md != null" },
{
"type": "alignmentMismatches",
"copyFields": [
"chrom",
"_lane",
"name",
"cigar",
"mapq",
"strand"
]
},
{
"type": "formula",
"expr": "datum.baseQuality == null ? 20 : datum.baseQuality",
"as": "_baseQualityForOpacity"
},
{
"type": "filter",
"expr": "datum.baseQuality == null || datum.baseQuality >= minBaseQuality"
}
],
"encoding": {
"x": {
"chrom": "chrom",
"pos": "mismatchStart",
"type": "locus",
"band": 0
},
"x2": { "chrom": "chrom", "pos": "mismatchEnd", "band": 0 }
},
"layer": [
{
"title": "Mismatch",
"mark": {
"type": "rect",
"minWidth": 1
},
"encoding": {
"color": {
"field": "base",
"type": "nominal",
"scale": {
"domain": ["A", "C", "T", "G", "N"],
"range": [
"#4FBF45",
"#4D96E8",
"#E85F78",
"#E8B322",
"#BDBDBD"
]
},
"legend": { "title": "Base" }
},
"opacity": {
"field": "_baseQualityForOpacity",
"type": "quantitative",
"scale": {
"domain": [5, 20],
"range": [0.1, 1],
"clamp": true,
"nice": false
},
"legend": {
"title": "Base quality",
"values": [6, 10, 15, 20]
}
}
}
},
{
"title": "Mismatch base",
"mark": {
"type": "text",
"color": "black",
"size": { "expr": "laneHeight * 0.75" },
"tooltip": null
},
"encoding": {
"text": { "field": "base", "type": "nominal" }
}
}
]
}
]
},
{
"name": "zoom-message",
"data": { "values": [{}] },
"params": [
{
"name": "zoomMessageState",
"expr": "abs(span(domain('x'))) > windowSize ? 1 : 0",
"transition": { "type": "lerp", "halfLife": 60 }
}
],
"opacity": { "expr": "zoomMessageState" },
"layer": [
{
"mark": {
"type": "rect",
"fill": "white",
"opacity": 0.7
}
},
{
"mark": {
"type": "text",
"text": "Zoom in closer to load data.",
"color": "#333",
"size": 13,
"yOffset": 20
},
"encoding": {
"x": { "value": 0.5 },
"y": { "value": 1 }
}
}
]
}
]
}
],
"config": {
"view": { "stroke": "lightgray" },
"legend": {}
}
}
The example uses a small BAM slice derived from public Genome in a Bottle /
NIST HG002 (NA24385) Illumina 300x whole-genome alignments. The slice covers
chr20:9950000-10100000 on GRCh38 and is downsampled for browser-based
visualization. It is intended only as a visualization demo, not for clinical
interpretation, diagnostic decisions, variant calling, biological
inference, benchmarking, genealogy, or re-identification.
Source attribution: Genome in a Bottle / NIST HG002 (NA24385) data, and Zook, J.M. et al. Extensive sequencing of seven human genomes to characterize benchmark reference materials. Scientific Data 3, 160025 (2016). https://doi.org/10.1038/sdata.2016.25
What to notice¶
The top track summarizes read depth from aligned CIGAR blocks and stacks MD-derived mismatch support by alternate base. Insertions are summarized with vertical rules because they are anchored between reference bases and have zero reference width.
The read pileup uses arrow marks for strand direction and opacity for mapping quality. The mapping-quality slider filters low-confidence alignments. CIGAR-derived overlays mark deletions, skipped regions, insertions, and soft-clipped ends.
Mismatching bases are extracted from MD tags and rendered as colored ranged rectangles with base letters on top. Their opacity follows base quality, and the base-quality slider can hide low-quality mismatch evidence. When the visible region is wider than the configured BAM loading window, the read track fades in a zoom-in message.
GenomeSpy Features¶
This example combines several GenomeSpy capabilities in one alignment view:
- Lazy data sources load BAM alignments only for
the visible region. The BAM loading window is controlled by an
ExprRefso the same parameter can drive the zoom message. - Parameters bind mapping-quality and base-quality thresholds to sliders.
pileupassigns reads to non-overlapping lanes.flattenCigarexpands CIGAR strings into operation rows for coverage and read overlays.alignmentMismatchesextracts sparse mismatch rows from the read sequence, CIGAR string, and MD tag.coverage,aggregate, andstacksummarize depth and mismatch support.formuladerives helper fields for mapping-quality and base-quality opacity.layercomposes coverage, insertion summaries, read bodies, CIGAR annotations, and mismatch labels while keeping color and opacity scales independent where needed.