Init & persistence
A richer initial view, then saving or sharing the session.
Advanced init
View source — 37 lines
import { LinearGenomeView } from '@jbrowse/react-linear-genome-view2'
export default function WithInitAdvanced() {
return (
<LinearGenomeView
assembly={{
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
}}
tracks={[
{
type: 'FeatureTrack',
trackId: 'ncbi-refseq-genes',
name: 'NCBI RefSeq Genes',
assemblyNames: ['hg38'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz',
},
},
]}
init={{
loc: 'chr1:11,106,077-11,261,675',
tracklist: true,
nav: true,
tracks: [
{ trackId: 'ncbi-refseq-genes', displaySnapshot: { height: 200 } },
],
highlight: ['chr1:11,170,000-11,190,000'],
}}
/>
)
}Session highlights
View source — 63 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
export default function WithSessionHighlights() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
aliases: ['GRCh38'],
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
type: 'FeatureTrack',
trackId: 'ncbi-refseq-genes',
name: 'NCBI RefSeq Genes',
assemblyNames: ['hg38'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz',
},
},
],
defaultSession: {
name: 'Session highlights',
view: {
type: 'LinearGenomeView',
highlight: [
{
assemblyName: 'hg38',
refName: 'chr1',
start: 11_130_000,
end: 11_145_000,
color: 'rgba(255, 0, 0, 0.25)',
label: 'Region of interest',
},
{
assemblyName: 'hg38',
refName: 'chr1',
start: 11_200_000,
end: 11_220_000,
color: 'rgba(0, 128, 255, 0.25)',
label: 'Promoter',
},
],
loc: 'chr1:11,106,077-11,261,675',
assembly: 'hg38',
tracks: [
{
trackId: 'ncbi-refseq-genes',
displaySnapshot: { height: 200 },
},
],
},
},
})
return state ? <JBrowseLinearGenomeView viewState={state} /> : null
}Persist & restore the session
The stored snapshot goes in session, not defaultSession, because its shape
is only known at runtime, and it restores against the same assembly and
tracks. The example removes the entry before using it, so a snapshot this
build cannot open fails once rather than on every reload.
View source — 91 lines
import { useEffect } from 'react'
import { useCreateOnceAsync } from '@jbrowse/core/util/hooks'
import { getSnapshot, onSnapshot } from '@jbrowse/mobx-state-tree'
import {
JBrowseLinearGenomeView,
createViewStateAsync,
} from '@jbrowse/react-linear-genome-view2'
const STORAGE_KEY = 'jbrowse-lgv-example-session'
const assembly = {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
}
const tracks = [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
]
const freshSession = {
name: 'Persisted session',
view: {
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1105..1221',
tracks: ['volvox_gff3'],
},
}
async function build() {
const saved = localStorage.getItem(STORAGE_KEY)
localStorage.removeItem(STORAGE_KEY)
try {
return await createViewStateAsync({
assembly,
tracks,
session: saved ? JSON.parse(saved) : undefined,
defaultSession: freshSession,
})
} catch (e) {
console.error(e)
return createViewStateAsync({
assembly,
tracks,
defaultSession: freshSession,
})
}
}
export default function WithSessionPersistence() {
const state = useCreateOnceAsync(build)
useEffect(() => {
if (state) {
const save = (snap: unknown) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(snap))
}
save(getSnapshot(state.session))
return onSnapshot(state.session, save)
}
return undefined
}, [state])
return state ? (
<div>
<p>
Pan, zoom, or toggle tracks, then reload the page — the view comes back
where you left it.{' '}
<button
onClick={() => {
localStorage.removeItem(STORAGE_KEY)
location.reload()
}}
>
Reset saved session
</button>
</p>
<JBrowseLinearGenomeView viewState={state} />
</div>
) : null
}Put the session in the URL
A decoded session goes in session, not defaultSession, which validates
against a shape you wrote. The hash fragment never reaches the server, so a long
session cannot fail with HTTP 414. Only the session travels: the receiving page
supplies its own assembly and tracks.
View source — 140 lines
import { useEffect, useState } from 'react'
import {
JBrowseLinearGenomeView,
createViewStateAsync,
decodeSession,
destroyViewState,
encodeSession,
} from '@jbrowse/react-linear-genome-view2'
import type {
SessionSnapshot,
ViewModel,
} from '@jbrowse/react-linear-genome-view2'
const assembly = {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
}
const tracks = [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
]
const freshSession = {
name: 'Session in URL',
view: {
type: 'LinearGenomeView',
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_gff3'],
},
}
function readSessionParam() {
return (
new URLSearchParams(window.location.hash.slice(1)).get('session') ??
undefined
)
}
function writeSessionParam(value: string) {
const params = new URLSearchParams(window.location.hash.slice(1))
params.set('session', value)
window.history.replaceState(null, '', `#${params.toString()}`)
}
function build(session?: SessionSnapshot) {
return createViewStateAsync({
assembly,
tracks,
session,
defaultSession: session ? undefined : freshSession,
})
}
export default function SessionInUrl() {
const [state, setState] = useState<ViewModel | undefined>(undefined)
const [status, setStatus] = useState('')
useEffect(() => {
const mount = {
unmounted: false,
engine: undefined as ViewModel | undefined,
}
const open = (session?: SessionSnapshot) =>
build(session).then(
engine => {
if (mount.unmounted) {
destroyViewState(engine)
} else {
mount.engine = engine
setState(engine)
}
},
(e: unknown) => {
console.error(e)
setStatus(`could not open the view: ${e}`)
},
)
const param = readSessionParam()
if (param) {
decodeSession(param)
.then(session =>
open(session).then(() => {
setStatus(`restored "${session.name}" from the URL`)
}),
)
.catch((e: unknown) => {
console.error(e)
void open()
setStatus(`could not restore the session in the URL: ${e}`)
})
} else {
void open()
}
return () => {
mount.unmounted = true
if (mount.engine) {
destroyViewState(mount.engine)
}
}
}, [])
return state ? (
<div>
<div style={{ padding: 8, fontSize: 13, background: '#8881' }}>
<button
type="button"
onClick={() => {
void encodeSession(state)
.then(encoded => {
writeSessionParam(encoded)
setStatus(
`saved to the URL (${encoded.length} chars) — copy the address bar, or reload to restore it`,
)
})
.catch((e: unknown) => {
console.error(e)
setStatus(`could not save the session to the URL: ${e}`)
})
}}
>
Save this view to the URL
</button>{' '}
{status || 'navigate or toggle a track, then save'}
</div>
<JBrowseLinearGenomeView viewState={state} />
</div>
) : null
}