Controlling the view
Navigate, zoom and toggle tracks from your own UI, and read clicks back.
A location box, zoom buttons and a track list
View source — 151 lines
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { LocationBox, RegionSeams, TrackStack } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
const catalogue = [
{ id: 'hg38_phylop', label: 'Conservation' },
{ id: 'hg38_genes', label: 'Genes' },
{ id: 'na12878_exome', label: 'Reads' },
]
const bookmarks = [
{ label: 'BRCA1', loc: 'chr17:43,044,295..43,125,364' },
{ label: 'A whole chromosome', loc: 'chr17' },
{
label: 'Two regions at once',
loc: 'chr17:43,044,295..43,060,000 chr17:43,100,000..43,125,364',
},
{
label: 'Two BRCA genes',
loc: 'chr17:43,044,295..43,125,364 chr13:32,315,474..32,400,266',
},
]
const Toolbar = observer(function Toolbar({
view,
}: {
view: LinearGenomeViewModel
}) {
const shown = new Set(view.tracks.map(t => t.configuration.trackId))
return (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: 12,
paddingBottom: 8,
fontSize: '0.85rem',
}}
>
<LocationBox view={view} />
<span>
<button
type="button"
aria-label="Zoom out"
onClick={() => {
view.zoom(view.bpPerPx * 2)
}}
>
−
</button>
<button
type="button"
aria-label="Zoom in"
onClick={() => {
view.zoom(view.bpPerPx / 2)
}}
>
+
</button>
</span>
{bookmarks.map(({ label, loc }) => (
<button
key={label}
type="button"
onClick={() => {
view.navToLocString(loc).catch((e: unknown) => {
console.error(e)
})
}}
>
{label}
</button>
))}
{catalogue.map(({ id, label }) => (
<label key={id}>
<input
type="checkbox"
checked={shown.has(id)}
onChange={() => {
if (shown.has(id)) {
view.hideTrack(id)
} else {
void view.launchTrack(id)
}
}}
/>
{label}
</label>
))}
</div>
)
})
const DriveItFromYourApp = observer(function DriveItFromYourApp() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_phylop',
name: 'phyloP 100-way conservation',
uri: 'https://hgdownload.soe.ucsc.edu/goldenpath/hg38/phyloP100way/hg38.phyloP100way.bw',
displayDefaults: { height: 100, color: '#3a7ca5' },
},
{
trackId: 'hg38_genes',
name: 'RefSeq curated genes',
uri: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz',
index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz.csi',
displayDefaults: { height: 120 },
},
{
trackId: 'na12878_exome',
name: 'NA12878 exome reads',
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/alignments/NA12878/NA12878.alt_bwamem_GRCh38DH.20150826.CEU.exome.cram',
displayDefaults: { height: 150 },
},
],
init: {
loc: 'chr17:43,044,295..43,125,364',
tracks: ['hg38_phylop', 'hg38_genes'],
},
})
if (!state) {
return null
}
const { session } = state
return (
<SessionPaletteProvider session={session}>
<DisplayUIProvider>
<Toolbar view={session.view} />
<TrackStack view={session.view}>
<RegionSeams view={session.view} />
</TrackStack>
</DisplayUIProvider>
</SessionPaletteProvider>
)
})
export default DriveItFromYourAppThe whole genome at once
The demo spells out the chromosome list on purpose.
view.showAllRegionsInAssembly() would show all 455 sequences in hg38, and
every _alt, _random and chrUn_ scaffold elides into a grey smear. No field
in the FASTA says which sequences are the chromosomes.
View source — 52 lines
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { RegionSeams, Scalebar, TrackStack } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
const chromosomes = [
...Array.from({ length: 22 }, (_, i) => `chr${i + 1}`),
'chrX',
'chrY',
]
const EveryChromosome = observer(function EveryChromosome() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_phylop',
name: 'phyloP 100-way conservation',
uri: 'https://hgdownload.soe.ucsc.edu/goldenpath/hg38/phyloP100way/hg38.phyloP100way.bw',
displayDefaults: { height: 120, color: '#3a7ca5' },
},
],
init: {
loc: chromosomes.join(' '),
tracks: ['hg38_phylop'],
},
})
if (!state) {
return null
}
const { session } = state
return (
<SessionPaletteProvider session={session}>
<DisplayUIProvider>
<TrackStack view={session.view}>
<Scalebar view={session.view} />
<RegionSeams view={session.view} />
</TrackStack>
</DisplayUIProvider>
</SessionPaletteProvider>
)
})
export default EveryChromosomeFeature details on click
View source — 118 lines
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { isFeature } from '@jbrowse/core/util/simpleFeature'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { TrackStack } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
const hidden = new Set([
'refName',
'start',
'end',
'strand',
'type',
'name',
'uniqueId',
])
const FeatureDetails = observer(function FeatureDetails({
session,
}: {
session: { selection: unknown; clearSelection: () => void }
}) {
const { selection } = session
if (!isFeature(selection)) {
return <div style={{ padding: 12, opacity: 0.7 }}>Click a gene.</div>
}
const data = selection.toJSON()
const rows = Object.entries(data).filter(
([key, value]) =>
!hidden.has(key) && value !== null && typeof value !== 'object',
)
return (
<div style={{ padding: 12, fontSize: '0.8rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<strong>{data.name ?? data.type ?? 'Feature'}</strong>
<button
type="button"
onClick={() => {
session.clearSelection()
}}
>
Clear
</button>
</div>
<div style={{ opacity: 0.75 }}>
{data.refName}:{data.start.toLocaleString()}-{data.end.toLocaleString()}
</div>
<dl
style={{
display: 'grid',
gridTemplateColumns: 'auto 1fr',
gap: '2px 10px',
}}
>
{rows.map(([key, value]) => (
<div key={key} style={{ display: 'contents' }}>
<dt style={{ opacity: 0.7 }}>{key}</dt>
<dd style={{ margin: 0, wordBreak: 'break-word' }}>
{String(value)}
</dd>
</div>
))}
</dl>
</div>
)
})
const YourOwnFeatureDetails = observer(function YourOwnFeatureDetails() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_genes',
name: 'RefSeq curated genes',
uri: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz',
index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz.csi',
displayDefaults: { height: 180 },
},
],
init: {
loc: 'chr17:43,044,295..43,125,364',
tracks: ['hg38_genes'],
},
})
if (!state) {
return null
}
const { session } = state
return (
<SessionPaletteProvider session={session}>
<DisplayUIProvider>
<div style={{ display: 'flex' }}>
<TrackStack view={session.view} style={{ flex: 1, minWidth: 0 }} />
<div
style={{
width: 260,
flex: 'none',
overflow: 'auto',
borderLeft:
'1px solid color-mix(in srgb, currentColor 25%, transparent)',
}}
>
<FeatureDetails session={session} />
</div>
</div>
</DisplayUIProvider>
</SessionPaletteProvider>
)
})
export default YourOwnFeatureDetailsA track selector sidebar
View source — 193 lines
import { useState } from 'react'
import { readConfObject } from '@jbrowse/core/configuration'
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { TrackStack } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
import type { ViewModel } from '@jbrowse/react-linear-genome-view2'
type Session = ViewModel['session']
const variantTrack = {
trackId: 'thousand_genomes_sv',
name: 'Structural variants',
category: ['Variants'],
assemblyNames: ['hg38'],
uri: 'https://ftp.1000genomes.ebi.ac.uk/vol1/ftp/data_collections/1000G_2504_high_coverage/working/20210124.SV_Illumina_Integration/1KGP_3202.gatksv_svtools_novelins.freeze_V3.wAF.vcf.gz',
displayDefaults: { height: 90 },
}
function listTracks(session: Session) {
return session.tracks.map(conf => {
const trackId: string = readConfObject(conf, 'trackId')
const [category = 'Uncategorized'] = (readConfObject(conf, 'category') ??
[]) as string[]
return {
trackId,
category,
name: (readConfObject(conf, 'name') as string) || trackId,
}
})
}
const TrackSelector = observer(function TrackSelector({
session,
}: {
session: Session
}) {
const [filter, setFilter] = useState('')
const { view } = session
const shown = new Set(view.tracks.map(t => t.configuration.trackId))
const needle = filter.trim().toLowerCase()
const groups = Map.groupBy(
listTracks(session).filter(e =>
`${e.name} ${e.category}`.toLowerCase().includes(needle),
),
e => e.category,
)
return (
<div
style={{
width: 220,
flex: 'none',
display: 'flex',
flexDirection: 'column',
gap: 8,
padding: 10,
fontSize: '0.8rem',
}}
>
<input
aria-label="Filter tracks"
placeholder="Filter tracks"
value={filter}
onChange={event => {
setFilter(event.target.value)
}}
/>
{[...groups].map(([category, entries]) => (
<div key={category}>
<strong>{category}</strong>
{entries.map(({ trackId, name }) => (
<label key={trackId} style={{ display: 'block' }}>
<input
type="checkbox"
checked={shown.has(trackId)}
onChange={() => {
if (shown.has(trackId)) {
view.hideTrack(trackId)
} else {
void view.launchTrack(trackId)
}
}}
/>
{name}
</label>
))}
</div>
))}
<button
type="button"
disabled={!!session.getTrackById(variantTrack.trackId)}
style={{ marginTop: 'auto' }}
onClick={() => {
session.addSessionTrackConf(variantTrack)
}}
>
Add a variant track
</button>
</div>
)
})
const TrackSelectorSidebar = observer(function TrackSelectorSidebar() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_phylop',
name: 'phyloP conservation',
category: ['Signal'],
uri: 'https://jbrowse.org/demos/phylop/hg38.phyloP100way.brca1.bw',
displayDefaults: { height: 80, color: '#3a7ca5' },
},
{
trackId: 'hg38_gnomad_genome_coverage',
name: 'gnomAD genome coverage',
category: ['Signal'],
uri: 'https://hgdownload.soe.ucsc.edu/gbdb/hg38/gnomAD/coverage/v3-genome/gnomad.coverage.mean.bw',
displayDefaults: { height: 80 },
},
{
trackId: 'hg38_gnomad_exome_coverage',
name: 'gnomAD exome coverage',
category: ['Signal'],
uri: 'https://hgdownload.soe.ucsc.edu/gbdb/hg38/gnomAD/coverage/v4-exome/gnomad.coverage.mean.bw',
displayDefaults: { height: 80 },
},
{
trackId: 'hg38_genes',
name: 'RefSeq curated genes',
category: ['Annotation'],
uri: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz',
index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz.csi',
displayDefaults: { height: 120 },
},
{
trackId: 'hg38_segmental_dups',
name: 'Segmental duplications',
category: ['Annotation'],
uri: 'https://jbrowse.org/ucsc/hg38/genomicSuperDups.bed.gz',
index: 'https://jbrowse.org/ucsc/hg38/genomicSuperDups.bed.gz.csi',
displayDefaults: { height: 100 },
},
{
trackId: 'na12878_exome',
name: 'NA12878 exome reads',
category: ['Alignments'],
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/alignments/NA12878/NA12878.alt_bwamem_GRCh38DH.20150826.CEU.exome.cram',
displayDefaults: { height: 150 },
},
{
trackId: 'thousand_genomes_snvindels',
name: '1000 Genomes variants',
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/variants/ALL.wgs.shapeit2_integrated_snvindels_v2a.GRCh38.27022019.sites.vcf.gz',
displayDefaults: { height: 80 },
},
],
init: {
loc: 'chr17:43,044,295..43,125,364',
tracks: ['hg38_phylop', 'hg38_genes'],
},
})
if (!state) {
return null
}
const { session } = state
return (
<SessionPaletteProvider session={session}>
<DisplayUIProvider>
<div style={{ display: 'flex', minHeight: 330 }}>
<TrackSelector session={session} />
<TrackStack
view={session.view}
trackIds={listTracks(session).map(e => e.trackId)}
style={{ flex: 1, minWidth: 0 }}
/>
</div>
</DisplayUIProvider>
</SessionPaletteProvider>
)
})
export default TrackSelectorSidebar