sequenceTubeMap's layout: graph nodes and paths (and reads) in, tube map shapes out. No DOM, d3 or React.
pnpm add @jbrowse/tubemap-coreVersions 0.1.0 and 0.2.0 also went out as @gmod/tubemap-core, which is
deprecated; install this name instead.
import { layoutTubeMap } from '@jbrowse/tubemap-core'
const layout = layoutTubeMap(
[
{ name: '1', seq: 'ACGT' },
{ name: '2', seq: 'A' },
{ name: '3', seq: 'G' },
{ name: '4', seq: 'TTGCA' },
],
[
{ id: 0, name: 'ref', sequence: ['1', '2', '4'], sourceTrackID: 0 },
{ id: 1, name: 'alt', sequence: ['1', '3', '4'], sourceTrackID: 0 },
],
[], // reads
{ nodeWidthOption: 'compressed' },
)- Track 0 is the reference;
-nameis a reverse visit - A node without
seqneedssequenceLength
A TubeMapLayout, or undefined when no visible track is left. Every
coordinate is in layout pixels, x rightward and y downward.
| Field | Holds |
|---|---|
shapes |
What to draw: rectangles (straight tube segments), curves (tubes changing lane between nodes), corners and verticalRectangles (inversions). Each shape has coordinates and its track's id |
nodes |
The placed nodes, with x, y, pixelWidth, contentHeight, order and the tracks through each. Indexed from 1, with a hole at 0 |
tracks |
The haplotype tracks and placed reads, each with its path and width. A shape's id matches its track's id |
reads |
The placed reads alone |
bounds |
{ minX, maxX, minY, maxY } around everything drawn; size the SVG viewBox from it |
nodeMap |
Node name to index in nodes |
maxOrder, trackForRuler |
The count of horizontal order slots, and the name of the track a ruler takes coordinates from |
coarsened, coarsenedEdgeMeta |
The Coarsening of each banded layer and the label of every band; both are empty without layers or coarsenedReadView |
For the example above:
layout.bounds
// { minX: 0, maxX: 185.57, minY: 10, maxY: 85 }
layout.nodes[1]
// { name: '1', seq: 'ACGT', x: 20, y: 20, pixelWidth: 17, contentHeight: 30,
// order: 0, tracks: [0, 1], successors: [2, 3], ... }
layout.shapes.rectangles[0]
// { xStart: 0, yStart: 35, xEnd: 37, yEnd: 49, id: 1, name: 'alt',
// type: 'haplotype' }- Each shape holds its track's
idand no color, so a recolor needs no new layout layout.nodeshas a hole at index 0, so iterate it withforEachorfilter, notfor...oforfind
curvePaths(curves, type)fills in each curve's SVGpathnodeOutlinePath(node)returns a node's box as path data (new Path2D(d)on a canvas)- A renderer that squeezes the layout's x between columns passes the widest gap
it draws, in px, as
curvePaths(curves, type, { gapWidth }), so steep curves keep their width
The code below draws the example as SVG with these functions:
import { curvePaths, nodeOutlinePath } from '@jbrowse/tubemap-core'
const colors = ['#1f77b4', '#ff7f0e'] // by track id
const { minX, maxX, minY, maxY } = layout.bounds
const parts: string[] = []
for (const r of layout.shapes.rectangles) {
const w = r.xEnd - r.xStart + 1
const h = r.yEnd - r.yStart + 1
parts.push(
`<rect x="${r.xStart}" y="${r.yStart}" width="${w}" height="${h}" fill="${colors[r.id]}"/>`,
)
}
for (const c of curvePaths(layout.shapes.curves, 'haplotype')) {
parts.push(`<path d="${c.path}" fill="${colors[c.id]}"/>`)
}
layout.nodes.forEach(node => {
parts.push(`<path d="${nodeOutlinePath(node)}" fill="none" stroke="black"/>`)
})
const svg = `<svg xmlns="http://www.w3.org/2000/svg"
viewBox="${minX - 10} ${minY - 10} ${maxX - minX + 20} ${maxY - minY + 20}">
${parts.join('')}</svg>`Reads go in the third argument, and the code above draws them the same way.
Their shapes have type: 'read', so call
curvePaths(layout.shapes.curves, 'read') for their curves.
flattenPath(d, steps)turns any curve, corner or node outline into points, each Bézier split intostepsstraight pieces. Use it for renderers that fill polygons, such as matplotlib or R's grid.readMismatches(layout)places every read's substitutions, deletions and insertions. Each has the read'sid, itsxtoxEnd, theyandheightof the read's tube, and the substitutedseq.readMismatchesmarks an insertion at the read's first or last basesoftClip, a mark that sequenceTubeMap hides by default.
layoutTubeMap runs two phases. The package exports each one, so a caller can
run one topology under several placements:
const topology = layoutTopology(nodes, tracks, reads, topologyOptions)
const all = placeTubeMap(topology)
const confident = placeTubeMap(topology, { mappingQualityCutoff: 30 })layoutTopologymerges, orders, orients and sizes the nodes under the visible tracks and every primary read, and fixes each node's x from placing them allplaceTubeMaplays out the haplotypes and the reads its options keep at the topology's x positions, so every placement of one topology lines up- Read filters never move a node, so a node that only filtered-out reads visit keeps its place and is drawn empty
placeFacets places one topology once per subset and stacks the panels, each
with an offsetY, at the topology's x:
const { panels, bounds } = placeFacets(topology, { facetBy: 'sample_name' })facetBy:read_grouporsample_namesplits the reads and repeats every haplotype in each panel.haplotype_samplesplits the haplotypes by the sample in their PanSN names (parsePanSN), keeps the reference in every panel, and places the reads in a last panelPlacementOptions.facetplaces one panel alone, asplaceTubeMapdoes- Without
facetBy, or with nothing to split,panelsholds one panel ofplaceTubeMap's layout
nodeWidthOption:normal(default),compressed,small,fixedcharWidth: px per base undernormal(8.401)trackWidth: tube width (15)mergeNodes,showReads(true);coarsenedReadView,ignoreStrand(false)layers: the track sets to draw, each with an optional stat{ data: 'haplotypes', stat: 'coarsen' }bands every haplotype but the reference, one band per edge{ data: 'reads', stat: 'coarsen' }does the same for the reads- Reads stack under the haplotypes either way
- With
layersunset,coarsenedReadViewbands the reads, or the haplotypes when no reads load
mappingQualityCutoffandfocusReadNamesfilter reads. They are the only placement options, and the rest are topology options
pnpm install
pnpm lint
pnpm format:check
pnpm typecheck
pnpm build
pnpm test --run
pnpm test:packtest/layout.golden.test.tspins every shape and node position for the sequenceTubeMap demo examples and a few bundled graphs, under each option that picks a different layout pathscripts/dump-viewer-fixtures.tsregenerates the inputs intest/fixturesfrom a viewer checkout- After an intended layout change,
pnpm test --run -u test/layout.golden.test.tsrewrites the goldens intest/layout-golden
CHANGELOG.md lists the changes in each version.
pnpm version <patch|minor|major>runs the CI checks, bumpsversion, writes the changelog with git-cliff, tagsv<version>and pushes the tag.github/workflows/publish.ymlchecks that the tag names the package version, tests, and publishes to npm with provenance- A
releasejob then creates a GitHub release with notes copied fromCHANGELOG.md
- jbrowse-plugin-graphgenomeviewer - JBrowse 2 plugin that browses these graphs by locus
- sequenceTubeMap, MemPanG26 edition - our fork of the tube map app
- ggtubemap - tube maps as ggplot2 layers
- @jbrowse/bandage-core - Bandage layout and drawing engine
- @gmod/gbz-base - range-request reader
for
.gbz.dbdatabases
Tutorials on jbrowse.org
MIT
Derived from https://github.com/vgteam/sequenceTubeMap, keeping its original MIT copyright notice. docs/differences-from-upstream.md lists what this package adds and changes.
