# Custom track and display types

A new way to visualize data in an existing view is a display type; a track type
is a new conceptual category of track.

A track owns the high-level identity (an ID, a name, a default set of displays);
a display shows that track inside a particular view and owns the drawing.

```text
Track  ─owns→  Display(s)  ─draw→  canvas
```

Tracks are deliberately thin. Every in-tree registration, with the view each
display renders in — `SyntenyTrack` and `VariantTrack` are the two that reach
past `LinearGenomeView`:

<!-- DISPLAY_VIEW_TYPES START -->

<!-- prettier-ignore -->
| Track type | Display type | Renders in |
| --- | --- | --- |
| [](https://jbrowse.org/jb2/docs/config/alignmentstrack) | [](https://jbrowse.org/jb2/docs/config/linearalignmentsdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/featuretrack) | [](https://jbrowse.org/jb2/docs/config/linearbasicdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmanhattandisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmultirowfeaturedisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearscoredisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearwiggledisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/gccontenttrack) | [](https://jbrowse.org/jb2/docs/config/linearwiggledisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/gwastrack) | [](https://jbrowse.org/jb2/docs/config/linearmanhattandisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/hictrack) | [](https://jbrowse.org/jb2/docs/config/linearhicdisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/ldtrack) | [](https://jbrowse.org/jb2/docs/config/ldtrackdisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/maftrack) | [](https://jbrowse.org/jb2/docs/config/linearmafdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/multiquantitativetrack) | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearwiggledisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/quantitativetrack) | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearwiggledisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/referencesequencetrack) | [](https://jbrowse.org/jb2/docs/config/linearreferencesequencedisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/syntenytrack) | [](https://jbrowse.org/jb2/docs/config/chordsyntenydisplay) | CircularView |
|  | [](https://jbrowse.org/jb2/docs/config/dotplotdisplay) | DotplotView |
|  | [](https://jbrowse.org/jb2/docs/config/lgvsyntenydisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearsyntenydisplay) | LinearSyntenyView |
|  | [](https://jbrowse.org/jb2/docs/config/multiwaysyntenydisplay) | LinearGenomeView |
| [](https://jbrowse.org/jb2/docs/config/varianttrack) | [](https://jbrowse.org/jb2/docs/config/chordvariantdisplay) | CircularView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmarkdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearmultisamplevariantdisplay) | LinearGenomeView |
|  | [](https://jbrowse.org/jb2/docs/config/linearvariantdisplay) | LinearGenomeView |

<!-- DISPLAY_VIEW_TYPES END -->

Add a track type only when you need a new conceptual track category, a custom
config schema for that category, or behavior shared across multiple displays.
Register it with `pluginManager.addTrackType(...)`, reusing the base track
config schema.

Every track is given each display registered for its type. A display that can
draw from only some adapters declares the adapter
[capabilities](https://jbrowse.org/jb2/docs/developer_guides/creating_adapter) it needs, as the
pangenome graph display does with `adapterCapabilities: ['getSubgraph']`, and a
track whose adapter lacks one is not given it, so its **Display types** menu
does not offer it. A config that names the display keeps it.

## When to add a custom display type

- Drawing chrome over the rendered content (e.g. the Y-scale axis in wiggle
  tracks, soft-clip indicators in alignments)
- Adding track-menu items that toggle display-only state (e.g. "Show soft
  clipping", "Modifications")
- Wiring a [custom widget](https://jbrowse.org/jb2/docs/developer_guides/creating_widget) into feature
  clicks (e.g. `VariantFeatureWidget`)
- Bundling a specific adapter with drawing code tuned for it, so users get the
  right combination by default. The generic pairing a plugin skips here is
  `FeatureTrack` with `LinearBasicDisplay`.

The display owns view-specific state, menu items, overlays, and the drawing
itself. A rendering backend is built, never registered: the plugin ABI has no
rendering-backend element type, so the display declares what it draws as a mark
list and its component calls `createMarkBackend`, which walks the WebGPU →
WebGL2 → Canvas2D ladder over that list — or `createCanvas2DBackend` for a
drawing that is not instances of a shape. Per-base text over marks, such as the
reference sequence's letters, is an `OverlayCanvas` beside the backend.

## Display foundations

LGV (LinearGenomeView) displays compose one **foundation mixin** on
`BaseDisplay`, all sharing `baseLinearDisplayConfigSchema`. The foundation
answers how the display _fetches_; how it _renders_ is a separate axis on top.

<!-- DISPLAY_FOUNDATIONS START -->

<!-- prettier-ignore -->
| Foundation | Brings | Used by |
| --- | --- | --- |
| `MultiRegionDisplayMixin()` | Per-region fetch + render: the fetch autoruns, `rpcProps()` refetch wiring, and byte gating. The common case. | `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay`, `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearReferenceSequenceDisplay`, `LinearScoreDisplay`, `LinearWiggleDisplay` |
| `GlobalFetchMixin()` | One non-regional dataset with no per-region partitioning, plus the render lifecycle. Installs no fetch autoruns; the display adds its own via `installGlobalFetchAutorun`. | `LDTrackDisplay`, `LinearHicDisplay`, `MultiWaySyntenyDisplay` |
| `ComparativeFetchMixin()` | One single-payload fetch keyed on both views' state, drawn onto a canvas the containing view owns — no render lifecycle and no byte gate here. Installs no fetch autoruns; the display adds its own via `installComparativeFetchAutorun`. | `DotplotDisplay`, `LinearSyntenyDisplay` |

<!-- DISPLAY_FOUNDATIONS END -->

The two walkthroughs below both use `MultiRegionDisplayMixin`, the common case.
The
[architecture spec](https://github.com/GMOD/jbrowse-components/blob/main/agent-docs/ARCHITECTURE.md#display-stacks)
goes further into why fetch and render are split.

If your display holds onto what the pointer is over, read
[the hover rules](https://github.com/GMOD/jbrowse-components/blob/main/agent-docs/reference/DISPLAY_HOVER.md)
first: a zoom, a side-scroll, an internal scroll and the region-too-large banner
all move content under a stationary cursor without firing a pointer event, so a
stored hit goes on naming what used to be there.

## Cross-cutting mixins

Orthogonal to the foundation — compose any of them on top of whichever one you
picked. Each is one mixin with one overridable hook, and composing it **is** the
opt-in: a display that never overrides the hook gets the default at no extra
cost. Reach for one before writing the behavior yourself; **Composed by** is
read off the `types.compose(...)` calls, so it also answers "does anything else
already do this?"

<!-- CROSS_CUTTING_MIXINS START -->

<!-- prettier-ignore -->
| Mixin | The display supplies | Composed by |
| --- | --- | --- |
| `TrackHeightMixin()` | Internal vertical scroll. `scrollContentHeight` and `scrollViewportHeight` (both default 0 = doesn't scroll). Brings the derived `scrollableHeight`, the clamped `setScrollTop` and the autorun that re-clamps when content shrinks | `LDTrackDisplay`, `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay`, `LinearHicDisplay`, `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearReferenceSequenceDisplay`, `LinearScoreDisplay`, `LinearWiggleDisplay`, `MultiWaySyntenyDisplay` |
| `LegendMixin()` | The legend, whole. A display declares the color scales it paints with (`colorScales`, a getter hook, and `colorScalesIn` where they follow the theme) and the mixin derives the key from them (`legendSpec`, and `legendSpecIn` for the SVG export, through `legendSpecOf`), keeps the `showLegend` slot's getter and setter, dismisses sections one at a time (`dismissLegendSection`, undone by re-showing the legend), answers whether there is a key to offer (`hasLegendKey`) and whether the export parks it beside the plot (`svgLegendWidth`). `DisplayChrome` draws the on-screen key and `renderDisplaySvg` the exported one, so a display places neither | `LDTrackDisplay`, `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay`, `LinearHicDisplay`, `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearWiggleDisplay`, `MultiWaySyntenyDisplay` |
| `ContextMenuMixin()` | The right-click state of a display whose menu acts on a | `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay`, `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearWiggleDisplay` |
| `StoredHoverMixin()` | A stored hover. The hit type, as the type parameter. Brings the `hoveredFeature` getter `BaseDisplay` declares as a hook, `setHoveredFeature`, and the `clearHoveredFeature` the foundations' viewport-change reaction calls | `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearScoreDisplay`, `LinearWiggleDisplay` |
| `TreeSidebarMixin()` | Row set with a dendrogram sidebar, its arrangement the display's `rows` config object and its row colors the `rowColor` object, each written as a session edit to the track's config so undo, reset and a share link reach it and it survives unticking the track. Brings the sidebar toggles, the `runClustering` / `clusterRegion` and `sortRowsBy` declarative launch specs `setupTreeSidebarAutoruns` consumes, the row arrangement every shared consumer goes through, the rows derived from it (`editableSources`, `clusterableSources`) with the arrangement dialog's `applyRowEdits`, the `root` getter, and the tree-hover and canvas-ref volatiles the shared sidebar draws through. A display supplies `discoveredRows`, and `guideTreeNewick` where its adapter carries a tree, and overrides the hooks its rows need | `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay`, `LinearWiggleDisplay` |
| `RowHeightMixin()` | The two-valued row height every multi-row display has. A `rowHeightConfigSchemaFields` slot whose `0` means fit-to-display-height, and an `autoRowHeight` getter saying what that fit divides. Brings the raw `rowHeight` getter, `setRowHeight`, and the resolved `effectiveRowHeight` every consumer reads | `LinearMafDisplay`, `LinearMarkDisplay`, `LinearMultiRowFeatureDisplay`, `LinearMultiSampleVariantDisplay` |
| `HiddenGroupsMixin()` | The sections a reader hid from an in-track grouping's chips: the `hiddenGroups` set, `hideGroup` and `showAllGroups` over it, the `displayHiddenGroupKeys` hook a display hides a lane through on its own behalf, `hiddenGroupKeys` folding both, `groupStateKey` (with the `ownGroupState` hook) for a live figure to key on, and the `dropGroupState` reset that fires when the host's `groupKeySpace` moves | `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay`, `LinearMarkDisplay` |
| `ScoreScaleMixin()` | Value scale, written in `scales.y`. `valueScaleSchema` / `scalesSchema`. Brings `ScoreAxisMixin` plus `scaleType` / `scaleZero` / `domainQuantile` / `clipQuantile` / `symlogConstant` / `manual*` and their setters, i.e. the whole `ScoreScaleModel` interface the Y axis row and its drawer widget consume | `LinearAlignmentsDisplay`, `LinearMarkDisplay`, `WiggleScoreConfigMixin` |
| `HeightModeMixin()` | Track-height strategy; the one row that must compose **after** `TrackHeightMixin()`, whose `height` and `resizeHeight` it overrides. `growTargetHeight` (default = the raw slot). Brings `heightMode`/`autoHeight`/`fitHeightToDisplay`, `grownHeight`, the reactive `height` override, `setHeightMode`, and the grow-aware `resizeHeight`, and the grow-exit bake reaction that writes the grown height into the slot when the mode leaves grow | `LinearAlignmentsDisplay`, `LinearCanvasBaseDisplay` |
| `TriangleMatrixMixin()` | A matrix drawn as a triangle over the view's axis (Hi-C, LD): the fetched payload (`rpcData`), the canvas box (`canvasWidth`, `matrixHeight` under the `matrixTop` hook), the rotate-and-squash transform and its inverse (`cellToScreen`, `screenToCell`), the frame the marks read (`triangleFrame`), the one-cell region map and canvas-wide block the mark backend draws (`matrixRegions`, `matrixBlocks`), and the `squashToHeight` slot. Composes after `TrackHeightMixin`, `GlobalFetchMixin` and `LegendMixin` | `LDTrackDisplay`, `LinearHicDisplay` |

<!-- CROSS_CUTTING_MIXINS END -->

Order matters in one place: `types.compose` gives a collision to the later
argument, so composing `HeightModeMixin()` before `TrackHeightMixin()` leaves
grow mode inert. The mixin reports the ordering problem at attach.

## Walkthroughs

Two end-to-end guides build the same display, differing only in whether the
display writes a shape of its own. Start with the first:

- [](https://jbrowse.org/jb2/docs/developer_guides/plotting_features) - fetch in a worker, declare a
  mark over a shared shape. Most displays stop here.
- [](https://jbrowse.org/jb2/docs/developer_guides/creating_gpu_display) - the same display over a
  shape of its own: a `.slang`, a uniform write, a painter and a hit test.

Both are build-step plugins; [](https://jbrowse.org/jb2/docs/developer_guides/simple_plugin) covers the
scaffold and build setup they assume.

For a worked in-tree display, read `plugins/wiggle/src/LinearWiggleDisplay` for
an overlay drawn over rendered content, or
`plugins/alignments/src/LinearAlignmentsDisplay` for many toggleable menu items
and a custom feature widget.

## See also

- [](https://jbrowse.org/jb2/docs/developer_guides/data_fetching)
- [](https://jbrowse.org/jb2/docs/developer_guides/svg_export)

