Custom view types
TL;DR: View types are top-level "panels" the session can contain alongside
built-ins like LinearGenomeView, DotplotView, and CircularView. A view
defines its own state model and React component; displaying genomic tracks is
optional.
When to add a custom view type
Add a view type when you need a panel with its own layout, state, and toolbar that does not fit inside an existing view. Examples:
jbrowse-plugin-msaviewadds a multiple sequence alignment view that has no underlying tracks at allDotplotViewandLinearSyntenyViewhost synteny tracks but with their own axis and layout logic
To render features differently inside the linear genome view, use a custom display type.
Minimal walkthrough
The plugin templates scaffold the build
setup to register a view via pluginManager.addViewType(...). Every built-in
view is registered the same way — this is the dotplot's, in full:
import { lazy } from 'react'
import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType'
import { dotplotLaunchKeys } from './launchKeys.ts'
import type PluginManager from '@jbrowse/core/PluginManager'
import type { ViewTypeRegistry } from '@jbrowse/core/PluginManager'
export default function DotplotViewF(pluginManager: PluginManager) {
pluginManager.addViewType(() => {
// annotated against the registry rather than inferred, which is what
// makes a hand-written augmentation earn what `getViewType` promises
// its callers — see `ViewTypeRegistry`
const stateModel = (): Promise<ViewTypeRegistry['DotplotView']> =>
import('./model.ts').then(f => f.default(pluginManager))
return new ViewType({
name: 'DotplotView',
displayName: 'Dotplot view',
stateModel,
launchKeys: dotplotLaunchKeys,
ReactComponent: lazy(() => import('./components/DotplotView.tsx')),
})
})
}
ViewType takes four core options:
name— what a session snapshot and a URL spec store.displayName— what the view launcher's dropdown shows.stateModel— a mobx-state-tree model, see MST patterns. Pass a function returning a promise for one, as the snippet does, and it is fetched when a session first names the type instead of at install — which is what keeps the model out of the initial bundle, and what makeslaunchViewrather thanaddViewthe call that opens the view.ReactComponent— receives{ model }as a prop. Wrap it inReact.lazyas every built-in view does, so the view's whole component tree stays out of the initial bundle until a session opens one.
ViewType takes two more options:
extendedNamenames another view type whose displays yours should also accept. Display types register against exactly one view type, so a subtype ofLinearGenomeViewneeds this to pick up the displays every track already has:addViewTypecollects the displays matching yournameor yourextendedName.viewMetadata: { hiddenFromGUI: true }keeps the type out of the view launcher's dropdown, for a view that only ever arrives from a spec, a connection, or another view's action.
The registry entry the snippet annotates against also types the call that opens
the view. await session.launchView('DotplotView', { ... }) checks the snapshot
against your state model, so a misspelled key is a compile error at the call
site rather than a key MST drops on attach; addView, replaceView and
addOrReplaceView read the same type. addView is the synchronous one and
throws for a lazily registered type, so it is only for a view whose stateModel
is the model itself. A name the registry does not carry still takes anything,
which is what a view keeps until it augments
ViewTypeRegistry.
Making the view launchable from a session spec
Registering the view type is what lets a session snapshot restore one. Opening
one from a URL is separate: loadSessionSpec dispatches on the spec's type to
a LaunchView-<name> extension point, and a view type with no registered point
cannot be launched from a spec; the error names the view type.
Register one to make yours launchable, exporting the args interface and
augmenting ExtensionPointRegistry beside it. The spreadsheet view's launcher
is the worked example, under
TypeScript types for extension points;
the LaunchView points
covers what the launcher is handed and which spec keys never reach it.
Telling the app when the view is ready
The app's ready marker, which screenshots, the capture tools and an agent's
jb.waitReady all wait on, reads two optional flags off every open view:
initialized === false and showLoading === true both count as still loading.
Neither is part of the base view model. A view whose content arrives
asynchronously (a structure viewer, a remote layout) should expose showLoading
as a getter that is true until the content has drawn; otherwise the app reports
ready while the view is blank, and an agent driving it falls back to guessing
sleeps.
Reference implementations in this repo
plugins/linear-genome-view/src/LinearGenomeView- the canonical genomic view, with displayed regions, blocks, and a track containerplugins/dotplot-view/src/DotplotView- independent X/Y axes hosting synteny tracksplugins/spreadsheet-view/src/SpreadsheetView- non-genomic tabular viewplugins/circular-view/src/CircularView- radial layout with chord tracks
See also
- Custom track and display types
- Extension points
- MST patterns
- Pluggable elements
- VIEW_INIT.md
— the launch state machine under the session spec above, and where
afterAttachsits in it - REGION_VIEW_LAUNCH.md — the convention for opening another view type on a locus, where the two existing launchers diverge, and what is still open