JBrowse 2 · Linear Genome View examples

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
}