Custom track and display types
TL;DR: 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.
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:
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.
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 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
FeatureTrackwithLinearBasicDisplay.
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 calls createRenderingBackend,
which walks the WebGPU → WebGL2 → Canvas2D ladder, or createCanvas2DBackend
when it ships no shader path.
Display foundations
LGV 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.
| Foundation | Brings | Used by |
|---|---|---|
MultiRegionDisplayMixin() | Per-region fetch + render: the fetch autoruns, rpcProps() refetch wiring, and byte gating. The common case. | LinearAlignmentsDisplay, LinearCanvasBaseDisplay, LinearMafDisplay, LinearManhattanDisplay, LinearMultiRowFeatureDisplay, LinearReferenceSequenceDisplay, LinearScoreDisplay, LinearWiggleDisplay, MultiLinearWiggleDisplay, MultiSampleVariantBaseModel |
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. | LinearArcDisplay, LinearHicDisplay, LinearPairedArcDisplay, MultiWaySyntenyDisplay, SharedLDModel |
Both walkthroughs use MultiRegionDisplayMixin, the common case. The
architecture spec
goes further into why fetch and render are split.
If your display holds onto what the pointer is over, read the hover rules 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 and pays
nothing. 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?"
| Mixin | The display supplies | Composed by |
|---|---|---|
TrackHeightMixin() | Internal vertical scroll. scrollableHeight (default Infinity = doesn't scroll). Brings the clamped setScrollTop and the autorun that re-clamps when content shrinks | LinearAlignmentsDisplay, LinearArcDisplay, LinearCanvasBaseDisplay, LinearHicDisplay, LinearMafDisplay, LinearManhattanDisplay, LinearMultiRowFeatureDisplay, LinearPairedArcDisplay, LinearReferenceSequenceDisplay, LinearScoreDisplay, LinearWiggleDisplay, MultiLinearWiggleDisplay, MultiSampleVariantBaseModel, MultiWaySyntenyDisplay, SharedLDModel |
LegendMixin() | A legend the user can turn off. A promotable showLegend config slot, whose promotedBase sets whether this display type's legend is on by default. Brings the resolved showLegend getter, the showLegendDisplayTypeDefault pin showLegendCheckboxItem takes, and setShowLegend | LinearAlignmentsDisplay, LinearCanvasBaseDisplay, LinearHicDisplay, LinearMultiRowFeatureDisplay, MultiLinearWiggleDisplay, MultiSampleVariantBaseModel, SharedLDModel |
TreeSidebarMixin() | Row set with a dendrogram sidebar. sources (the display rows, named), the three treeSidebarConfigSchemaFields slots, plus the run callback naming its own clustering RPC and the sortRows callback naming what a row carries at a column. Brings layout / clusterTree / clusterProvenance / treeAreaWidth / subtreeFilter, the showTree / showBranchLength / showRowLabels getters and setters over those slots, the runClustering / clusterRegion and sortRowsBy declarative launch specs setupTreeSidebarAutoruns consumes, the root, willClearTree and rowOrderIsCustom getters, and the tree-hover and canvas-ref volatiles the shared sidebar draws through | LinearMafDisplay, LinearMultiRowFeatureDisplay, MultiLinearWiggleDisplay, MultiSampleVariantBaseModel |
ContextMenuMixin() | The right-click state of a display whose menu acts on a | LinearMafDisplay, LinearMultiRowFeatureDisplay, MultiLinearWiggleDisplay |
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, LinearMultiRowFeatureDisplay, MultiSampleVariantBaseModel |
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 | LinearAlignmentsDisplay, LinearCanvasBaseDisplay |
ScoreScaleMixin() | Score axis. Nothing — the config slots. Brings scaleType / autoscaleType / minScore / maxScore / *Bound / numStdDev and their setters, i.e. the whole ScoreScaleModel interface the shared score menu and SetMinMaxDialog consume | LinearAlignmentsDisplay, WiggleScoreConfigMixin |
Order matters in one place: types.compose gives a collision to the later
argument, so composing HeightModeMixin() before TrackHeightMixin() silently
leaves grow mode inert. The mixin reports that at attach.
Walkthroughs
Two end-to-end guides build the same display, differing only in the renderer. Start with the first:
- Plotting features in a custom display - fetch in a worker, draw with Canvas2D. Right for gene-scale tracks.
- GPU displays - the same display with a
.slangshader behind it, for roughly ≳100K features per frame.
Both are build-step plugins; Writing a 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.