JBrowse 2 · Linear Genome View examples

Navigate & control

Navigate to a region, lock down zoom and pan, and toggle tracks from your own code.

External navigation

A ref on <LinearGenomeView> gives you the live view model. Its .session.view can be read, mutated and driven from components outside the view tree — “jump to this gene” buttons, search-result lists, programmatic tours.

navToLocString takes what a user would type (ctgA:1-5,000, chr1:1m-2m). navToLocations takes { refName, start, end } objects, which skips a formatting round-trip when you already have coordinates from a backend; pass several to land in a multi-region view.

Both are async and both reject on input they can’t resolve, so a box with no .catch looks like it ignored the typo.

There is also a lower-level navTo that only moves within the currently displayed regions and won’t re-set them — rarely what external navigation wants. Anything marked #action in the state model is callable the same way.

View source — 92 lines
import { useRef } from 'react'

import { LinearGenomeView } from '@jbrowse/react-linear-genome-view2'

import type { ViewModel } from '@jbrowse/react-linear-genome-view2'

const assembly = {
  name: 'volvox',
  uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
}

// navigate with a JBrowse locstring
const bookmarks = [
  { label: 'ctgA — region A', loc: 'ctgA:1,000..5,000' },
  { label: 'ctgA — region B', loc: 'ctgA:20,000..25,000' },
  { label: 'ctgB — region C', loc: 'ctgB:1..2,000' },
]

// navigate with parsed {refName, start, end} coordinates you already have
const hits = [
  { label: 'gene1', refName: 'ctgA', start: 1050, end: 9000 },
  { label: 'gene2', refName: 'ctgA', start: 20000, end: 23000 },
  { label: 'gene3', refName: 'ctgB', start: 100, end: 1500 },
]

export default function ExternalNavigate() {
  const ref = useRef<ViewModel>(null)
  return (
    <div>
      <div style={{ marginBottom: 8 }}>
        <div>
          <code>navToLocString</code> (locstring):
        </div>
        {bookmarks.map(b => (
          <button
            key={b.loc}
            style={{ marginRight: 8 }}
            onClick={() => {
              ref.current?.session.view
                .navToLocString(b.loc)
                .catch((e: unknown) => {
                  console.error(e)
                })
            }}
          >
            {b.label}
          </button>
        ))}
      </div>
      <div style={{ marginBottom: 8 }}>
        <div>
          <code>navToLocations</code> (location object):
        </div>
        {hits.map(h => (
          <button
            key={h.label}
            style={{ marginRight: 8 }}
            onClick={() => {
              ref.current?.session.view
                .navToLocations(
                  [{ refName: h.refName, start: h.start, end: h.end }],
                  assembly.name,
                )
                .catch((e: unknown) => {
                  console.error(e)
                })
            }}
          >
            {h.label}
          </button>
        ))}
      </div>
      <LinearGenomeView
        ref={ref}
        assembly={assembly}
        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',
            },
          },
        ]}
        init={{ loc: 'ctgA:1,000..5,000' }}
      />
    </div>
  )
}

Disable zoom and side scroll

For a dashboard or report where the page — not the view — should own scroll and zoom, a small inline plugin overrides the view model’s scrollTo and zoomTo with no-ops. JBrowse plugins can wrap any state-model action, so this needs no change to the embedded component; register it through the plugins option.

The lock covers wheel zoom and click-drag side-scroll and leaves the rest of the view interactive — but it is not only a gesture lock. navTo and moveTo reach the view through those same two actions, so a locked view also stops responding to location and navToLocString: pin its starting scale and offset in a defaultSession instead.

Every header control routes through those two actions too, so the pan arrows, zoom buttons, zoom slider and search box go inert while still looking live. This demo sets hideHeader: true so they are not offered; the MiniControls that replaces the header keeps two zoom buttons, which stay inert.

If nothing in the view needs to be interactive, don’t lock a live one: export it to SVG, or render one ahead of time with the @jbrowse/img CLI, and put that image on the page.

See inline plugins for the general pattern.

View source — 66 lines
import Plugin from '@jbrowse/core/Plugin'
import { extendViewType } from '@jbrowse/core/pluggableElementTypes'
import { types } from '@jbrowse/mobx-state-tree'
import {
  JBrowseLinearGenomeView,
  useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'

import type PluginManager from '@jbrowse/core/PluginManager'

class MyPlugin extends Plugin {
  name = 'MyPlugin'
  install(pluginManager: PluginManager) {
    // #region extend
    extendViewType(pluginManager, 'LinearGenomeView', stateModel =>
      types.compose(
        stateModel,
        types.model().actions(() => ({
          zoomTo: () => {},
          scrollTo: () => {},
        })),
      ),
    )
    // #endregion
  }
  configure() {}
}

export default function WithDisableZoomAndSideScroll() {
  const state = useCreateViewState({
    assembly: {
      name: 'volvox',
      uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
    },
    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',
        },
      },
    ],
    plugins: [MyPlugin],
    // every header control routes through zoomTo/scrollTo, so with those
    // stubbed the pan arrows, zoom buttons, slider and search box are all
    // inert. MiniControls takes the header's place and keeps two zoom buttons
    defaultSession: {
      name: 'disable-zoom-and-side-scroll',
      view: {
        id: 'linearGenomeView',
        type: 'LinearGenomeView',
        hideHeader: true,
      },
    },
    // the lock is not only a gesture lock: navTo/moveTo reach the view through
    // the same two actions, so this picks the displayed region but the view
    // opens at its default scale rather than on this window. A view that has to
    // start somewhere specific wants its bpPerPx/offsetPx on the view above
    location: 'ctgA:1105..1221',
  })
  return <JBrowseLinearGenomeView viewState={state} />
}

Show a track programmatically

state.session.view.showTrack('my-track-id') opens a track in response to something at runtime — a button, a search hit, a prop — rather than at first mount. hideTrack is its counterpart. For tracks that should be open on first paint, list them in init instead.

For a track that isn’t in the tracks config at all (a file the user just picked, a hit from your own search service), register its config on the session first:

state.session.addTrackConf(trackConf)
state.session.view.showTrack(trackConf.trackId)

addTrackConf takes the same shape as the tracks prop, dedupes by trackId, and is what the built-in “add track” form uses. Session-added tracks round-trip through saved sessions.

View source — 61 lines
import {
  JBrowseLinearGenomeView,
  useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'

import type { ViewModel } from '@jbrowse/react-linear-genome-view2'

const TRACK_ID = 'volvox_gff3'

// `view.tracks` is observable, so an `observer` button knows whether the track
// is open without subscribing to anything — no callback, no local copy of the
// state that can fall out of step with the track selector's own checkbox.
const ToggleTrack = observer(function ToggleTrack({
  viewState,
}: {
  viewState: ViewModel
}) {
  const { view } = viewState.session
  const open = !!view.getTrack(TRACK_ID)
  return (
    <button
      onClick={() => {
        // showTrack API: https://jbrowse.org/jb2/docs/models/lineargenomeview/#action-showtrack
        if (open) {
          view.hideTrack(TRACK_ID)
        } else {
          view.showTrack(TRACK_ID)
        }
      }}
    >
      {open ? 'Hide' : 'Show'} the genes track
    </button>
  )
})

export default function WithShowTrack() {
  const state = useCreateViewState({
    assembly: {
      name: 'volvox',
      uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
    },
    tracks: [
      {
        trackId: TRACK_ID,
        name: 'Volvox genes',
        uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
      },
    ],
    // the view opens with the track closed, since this page is about opening it
    // from your own code. For a track that should be open on first paint, put
    // its id in `init.tracks` instead of calling showTrack at construction
    init: { loc: 'ctgA:1105..1221' },
  })
  return (
    <div>
      <ToggleTrack viewState={state} />
      <JBrowseLinearGenomeView viewState={state} />
    </div>
  )
}