Embedded components
The embedded components are React components on npm: one JSX element puts a genome browser in your page. They are the same views the full app is built from, so a track config that works there works here.
React 19 or newer is the only peer dependency. For a page that isn't a React
app, a <script> bundle includes its own React; see
non-React hosts.
Choosing a package
| Goal | Package | Component |
|---|---|---|
| One linear genome view | @jbrowse/react-linear-genome-view2, examples | <LinearGenomeView> |
| One circular genome view (e.g. SV chord diagrams) | @jbrowse/react-circular-genome-view2, examples | <CircularGenomeView> |
| Complete app (multiple view types, synteny, etc) | @jbrowse/react-app2, examples | <JBrowse> |
| Make your own custom UI around the engine | createViewState + @jbrowse/display-ui, examples | — |
Each package's storybook is the reference: copy-pasteable React code, one page per task (setting up the view, navigating from your own code, running a worker, theming, plugins).
A tracks entry is a track config, and the shortest one is { trackId, uri }.
The type and adapter come from the file's extension, and assemblyNames from
the component's one assembly (see
the shortest track).
@jbrowse/react-app2 has a stylesheet to import:
import '@jbrowse/react-app2/styles.css'. The stylesheet holds no rules today,
and the package ships any future CSS in it.
The props are initial values, like an input's defaultValue. The engine is
built once on first render and later prop changes are ignored. To switch
assembly or plugins on a mounted component, give it a React key that changes
with them so React remounts it.
Embedded views versus the full app
Embedded views are for genome browsing inside an existing page. For a standalone
browser, run JBrowse Web. @jbrowse/react-app2 sits
between: the whole app as a React component.
| Single-view components (LGV, CGV) | @jbrowse/react-app2 | JBrowse Web | |
|---|---|---|---|
| View types | One only | All of them, plugins included | All of them, plugins included |
| Feature detail | Opens in a dialog | Opens in a drawer | Opens in a left/right oriented drawer |
| Sessions | No built-in saving or loading | Held in your app's state, yours to persist | Save, import, export, plus local autosave |
| URLs | The page owns the URL | The page owns the URL | Reads URL params like &loc= and &session= |
Sessions and track manipulation are left to the embedding application. For Python or R, jbrowse-anywidget and JBrowseR wrap the same views.
Non-React hosts
Every package also exports an imperative controller — createLinearGenomeView,
createCircularGenomeView, createApp — with no JSX and no React root for you
to manage. The Python anywidget, R htmlwidgets, and
plain <script> pages are built on these controllers. See the vanillajs rows in
bundler examples below for a working reference
implementation of each.
Driving it from your code
createViewState takes the view to open as view, written with the same fields
a config or a link uses (Config and session JSON): where it opens
and which tracks it shows, with any of the view's own settings beside them. An
embedded view shows the loading spinner while the assembly loads:
const state = createViewState({
assembly,
tracks,
view: {
loc: 'chr1:1,000-2,000',
tracks: ['genes'],
highlight: ['chr1:1,500-1,600'],
hideHeader: true,
},
})locationandhighlightare shorthands forview.locandview.highlight, and the only two that also apply over adefaultSessionor a restoredsession.viewis the default session's view, so it excludesdefaultSession(Embedding JBrowse).jbrowseHub: 'hg38'in place ofassemblyfetches that genome from genomes.jbrowse.org: its sequence, refName aliases, track catalog and gene-name search, soview: { loc: 'BRCA1' }works andtracksadds your own files beside the hosted ones. Any UCSC database name or GenArk accession resolves. It is fetched, so it goes touseCreateViewStateorcreateViewStateAsync; the imperativecreateLinearGenomeViewtakes the same name as itsassembly. The circular view takes a list,jbrowseHub: ['hg38', 'mm39'], for two genomes on one circle.localFiles, aname -> bytesmap, serves a host whose data is in a process, not at a URL: a notebook kernel, an R session, anywhere with no web server and no CORS.tracksthen refers to a registered name as if it were a URL. Register an index under its conventional sibling name (peaks.bed.gz+peaks.bed.gz.tbi) and only the bytes the view needs are read.menuBardraws the app-shapedFilemenu bar above the embedded linear view, with the two items an embed can honour (open track, open connection);disableAddTracksempties that menu and removes the track selector's add-track affordances. Both are off by default.heightsets the view's height; it supersedes the olderdrawerViewHeight, which only applied while a drawer was open.- The imperative controllers take callbacks in place of props:
onLocationChangefires with the visible region as the user pans or zooms,onFeatureSelectwith the clicked feature,onSessionChangewith the view's layout whenever it settles (for a host offering "save this view"), andonErrorwhen the build itself fails, since building is asynchronous and that throw cannot reach your own call.createApp'sonPluginsUpdatedfires when something changes the running plugin set, with what a rebuild needs to remount the app on the new set.
Build your own
@jbrowse/display-ui/embed holds the
blocks of a view you draw yourself. createViewState builds the engine, and
these draw it:
EmbedProviderwraps the page. It mountsDisplayUIProvider(the plain, Material-free overlays a track draws while it loads or fails), the theme palette andNotifications.EngineBoundarywraps the component that callsuseCreateViewStateand draws a build that throws, such as a hub genome that does not exist.TrackStackdraws a view's tracks and handles pan and zoom.Track,Scalebar,LocationBox,Legend,ResizeHandleand the rest are the pieces to place beside it.
Install them as direct dependencies. Under pnpm a package your code imports must
be in your own package.json, and mobx-react is needed to write an observer
component:
pnpm add @jbrowse/react-linear-genome-view2@next @jbrowse/display-ui@next @jbrowse/core@next mobx mobx-react react react-domThe next tag is the v5 beta, and latest is v4, which has no
@jbrowse/display-ui/embed. Pin the exact version. An example that imports a
@jbrowse/plugin-* package needs that package added the same way. The
examples build from the main branch, so
they can run ahead of npm. Read the package's .d.ts to see what a release
exports.
Sizing. TrackStack sets overflow: hidden on its wrapper, and each track
takes the height in its displayDefaults. A track with more rows than fit,
such as a gene track with several isoforms at its default height, scrolls inside
itself. Set displayDefaults: { height } per track, add a ResizeHandle so the
reader can drag, and put the stack in a container that grows with its content.
Keeping an existing config. The uri and displayDefaults shorthand is
optional. The engine expands only a track that has a uri and no adapter, so
a track written in full, with its adapter, displays and type, or one on a
FromConfigAdapter, goes to createViewState unchanged. A
JBrowseLinearGenomeView track list moves over as it is.
A picture first, then the view
An embedded view shows an empty box until its JavaScript, its assembly and its
first track fetch have all arrived. A screenshot of the view's opening state
paints with the page instead, and the live view takes over once it has drawn.
Each package has a live example with the full source:
LGV,
CGV,
app and
build your own, where
PosterFirst from @jbrowse/display-ui/embed does everything below but the
screenshot.
- Shoot the poster from the view itself, at the width the embed will have.
waitForJBrowseReadyfrom@jbrowse/captureholds until every track has drawn. Ajb2exportfigure has no toolbar and uses different fonts, so the page would jump when the live view replaced it. - Load the engine when the box nears the viewport. An
IntersectionObserverplus a dynamicimport()(React.lazy) means a page with several embeds starts the engine for each one as the reader scrolls to it. Destructure what eachimport()returns (const { X } = await import('pkg')): a bundler keeps every export of a namespace it cannot see into, and loads those modules on page load wherever another page shares them. - Keep the poster up until the view reads ready. Each component publishes
[data-app-phase="ready"]once its tracks have drawn, or once a load has failed, so an error still reaches the reader. Wait for it withdata-app-viewsabove 0, then two animation frames, so the view's pending renders paint before the poster comes down. - Make the loading view
inert, so keyboard focus and screen readers stay out of a view nobody can see yet. - Hide the loading view with
opacity: 0. The LGV, CGV and app components setall: initialon their root to keep the host page's CSS out. That also resets an inheritedvisibility, so undervisibility: hiddenthe loading scrim shows through the poster. - Crop the poster on a narrower screen. Give it
width: 100%, the height it was shot at,object-fit: cover, and anobject-positionofleft topfor a linear view orcenter topfor the circle. The view keeps its height at any width, so the box keeps its height through the swap and the poster's text stays at full size.
The function that shoots each example's poster from its built page, with
launchBrowser and waitForJBrowseReady from @jbrowse/capture:
const browser = await launchBrowser()
try {
const page = await browser.newPage()
await page.setViewport({ width: 1400, height: 900, deviceScaleFactor: 2 })
await page.goto(url)
await waitForJBrowseReady(page)
// the page takes its old poster down once the view has drawn
await page.waitForFunction(
s => !document.querySelector(`${s} img`),
{},
embed,
)
const element = await page.$(embed)
if (!element) {
throw new Error(`no ${embed} on ${url}`)
}
await element.screenshot({ path: out })
const box = await element.boundingBox()
return { width: Math.round(box!.width), height: Math.round(box!.height) }
} finally {
await browser.close()
}Bundler examples
| Package | Bundler | Demo | Source |
|---|---|---|---|
| @jbrowse/react-app2 | next.js | demo | source |
| @jbrowse/react-app2 | vite | demo | source |
| @jbrowse/react-app2 | rsbuild | demo | source |
| @jbrowse/react-app2 | vanillajs | demo | source |
| @jbrowse/react-linear-genome-view2 | vite | demo | source |
| @jbrowse/react-linear-genome-view2 | rsbuild | demo | source |
| @jbrowse/react-linear-genome-view2 | next.js | demo | source |
| @jbrowse/react-linear-genome-view2 | vanillajs | demo | source |
| @jbrowse/react-circular-genome-view2 | vanillajs | demo | source |
| @jbrowse/react-circular-genome-view2 | next.js | demo | source |
| @jbrowse/display-ui (build your own) | vite | demo | source |
See also
- Tutorial: Embedding JBrowse
- Docs: jbrowse-anywidget: Python equivalent
- Docs: JBrowseR: R/Shiny equivalent