Skip to content
warelabPublic

About

gene tree React component

Resources

Contributing

Stars

0 stars

Watchers

4 watching

Forks

Repository files navigation

tbrowse

Embeddable React component for visualizing phylogenetic trees with pluggable data zones.

<TBrowse> renders a tree alongside a row-aligned set of zones (Tree, Labels, MSA, custom plug-ins). Tree topology, taxonomy, MSA, gene metadata, and per-node annotations are independent host inputs — TBrowse does no fetching of its own. View state (selection, collapse, prune, swap, zone widths, MSA viewport, search) is a single serializable object that round-trips through URL state via the controlled viewState / onViewStateChange props.

Status

2.0.0 is a complete rewrite of the original component on a fresh React 18 / TypeScript / Zustand stack. The 1.x line on npm (tbrowse@1.0.x) is the previous nwb / React 16 / Redux-Toolkit implementation, preserved at commit b017a74 for archival reference. The 2.0 API is not backwards-compatible with 1.x.

The rewrite is functionally complete: chassis, four built-in zones (Tree / Labels / MSA / Neighborhood), Ensembl REST and Gramene adapters, animated transitions, branch-length compression, gene-family coloring, protein-domain annotations, splice-junction marks, and a nodeOfInterest pivot. See CHANGELOG.md for the migration summary.

Quick start

import { TBrowse, treeZone, labelsZone, msaZone, type Tree, type MSA, type Taxonomy } from 'tbrowse';

const tree: Tree = { rootId: 'n0', nodes: { /* ... */ } };
const taxonomy: Taxonomy = { /* taxId → { scientificName, commonName, ... } */ };
const msa: MSA = { alphabet: 'protein', length: 60, sequences: { /* geneId → string */ } };

<div style={{ width: 900, height: 600 }}>
  <TBrowse
    tree={tree}
    taxonomy={taxonomy}
    msa={msa}
    zones={[treeZone, labelsZone, msaZone]}
  />
</div>

Live Ensembl example

The bundled Vite playground (npm run dev) has a "Load Ensembl tree" button that fetches a real gene tree (BRCA2 — ENSG00000139618), runs it through fromEnsemblGeneTree, and renders it pivoted to put human BRCA2 at the top. Demonstrates the entire stack against live data.

import { fromEnsemblGeneTree, computePivotState, TBrowse } from 'tbrowse';

const json = await fetch(
  'https://rest.ensembl.org/genetree/member/id/homo_sapiens/ENSG00000139618?aligned=1&sequence=protein',
  { headers: { Accept: 'application/json' } },
).then((r) => r.json());

const { tree, taxonomy, msa, geneMetadata } = fromEnsemblGeneTree(json);

Choosing a REST endpoint

rest.ensembl.org intermittently sheds load with 5xx errors, which is fatal to a fan-out that issues one request per leaf. The playground therefore ships a mirror picker listing the current endpoint plus every live per-release archive host. Archive hosts pin one Ensembl release and run on separate infrastructure, so they stay responsive when the main endpoint does not. The default is rest.ensembl.org (the current release), so the playground shows current data; switch to an archive when the main endpoint is struggling.

One caveat if you hard-code a host yourself: the gene-tree member route changed shape mid-history.

Release Route
≤ 109 /genetree/member/id/{gene}
110–111 either form
≥ 112 /genetree/member/id/{species}/{gene}

Sending the wrong form returns a 404, so the playground's URL builder switches on the selected release. Note that alignment width and leaf count differ between releases (BRCA2 is 174 leaves / 5471 columns on r112, 176 / 7138 on r105), so switching mirrors invalidates anything derived from the tree — the playground clears cached domains and gene structures on a switch.

All playground fetches go through a shared fetchWithRetry helper that retries 429 and 5xx with exponential backoff plus jitter, honouring Retry-After when present. The per-leaf fan-outs record individual failures and carry on rather than aborting the batch.

Development

npm install
npm run dev         # Vite dev server (examples/playground)
npm run typecheck   # tsc --noEmit
npm test            # vitest
npm run build       # library build to dist/

Host inputs

interface TBrowseProps {
  tree: Tree;                          // required
  taxonomy?: Taxonomy;
  msa?: MSA;
  geneMetadata?: GeneMetadata;
  nodeAnnotations?: NodeAnnotation[];
  labelProviders?: LabelProvider[];

  zones: ZoneDefinition[];             // built-ins + plugins
  nodeOfInterest?: string;             // pivot the tree to this gene/node at mount

  // Controlled / uncontrolled view state
  viewState?: ViewState;
  initialViewState?: Partial<ViewState>;
  onViewStateChange?: (next: ViewState) => void;

  theme?: 'light' | 'dark';
  className?: string;
}

Tree.nodes is flat: { id, parentId, distance, isLeaf, taxonomyId?, geneId?, eventType?, bootstrap? }. MSA.sequences is keyed by gene id. The Ensembl adapter (fromEnsemblGeneTree) walks the nested REST response into this normalized shape.

ViewState

The single serializable object that captures everything URL-shareable.

interface ViewState {
  selectedNodeId: NodeId | null;
  collapsedNodeIds: NodeId[];          // internal nodes shown as triangles
  prunedNodeIds: NodeId[];             // subtrees hidden + replaced with stub glyphs
  swappedNodeIds: NodeId[];            // internal nodes whose children render in reversed order
  zones: { id: string; width: number; visible: boolean }[];
  zoneStates: Record<string, unknown>; // per-zone state slot (built-ins use well-known keys)
  search: { field: string; query: string } | null;
}

All chassis interactions (resize, reorder, collapse via tooltip, prune via tooltip, swap, MSA pan/zoom, label-field picker, show/hide) write to this object via onViewStateChange. Persist it to a URL fragment for shareable views.

Built-in zones

Zone Id Default width
treeZone tree 280px
labelsZone labels 220px
msaZone msa 360px

The Tree zone supports collapse/expand, prune/regrow, swap children, click-tooltip with contextual actions, internal-node event glyphs (speciation circle / duplication square), proportional-height collapsed-subtree triangles, and animated transitions. Labels supports a configurable field picker (gene id, gene name, gene description, scientific name, common name) plus optional async LabelProvider plug-ins. MSA renders a canvas alignment with a coverage minimap (drag-to-pan), wheel-driven pan + ctrl/shift-zoom, and four built-in color schemes (Plain, Clustal, Hydrophobicity, Nucleotide).

Custom zones implement ZoneDefinition:

interface ZoneDefinition<S = unknown> {
  id: string;
  displayName: string;
  Header: ComponentType<ZoneRenderProps<S>>;
  Body: ComponentType<ZoneRenderProps<S>>;
  defaultWidth: number;
  minWidth: number;
  defaultZoneState: S;
  isAvailable: (data: HostData) => boolean;
}

Both Header and Body receive the same ZoneRenderProps: visibleRows (already collapsed/pruned/swapped), virtualization rowRange, hover/select callbacks, the host's data, and per-zone state plumbing.

Helpers

Export What
computeVisibleRows Pure derivation: (tree, collapsed, pruned, swapped) → VisibleRow[].
computeTreeLayout Pure phylogram layout against a column width.
computePivotState Resolve a gene id / node id and return collapse + swap sets that pivot the tree.
fromEnsemblGeneTree Convert Ensembl REST gene-tree JSON to TBrowse inputs.
createStubZone Build a placeholder zone for development.

Bundle

ESM + CJS, types via vite-plugin-dts. Peer-deps on react / react-dom only; runtime dep on zustand. Current bundle ~14 kB gzipped (CJS).

Project layout

src/
  TBrowse.tsx, types.ts, store.tsx, treeIndex.ts
  visibleRows.ts        chassis row derivation
  pivot.ts              nodeOfInterest helper
  layout/               grid + virtualization + animation
  zones/
    tree/               SVG branches, glyphs, triangles, tooltip
    labels/             field picker, label-provider cache
    msa/                canvas paint, minimap, color schemes
  adapters/
    ensembl.ts          Ensembl REST → TBrowse inputs
  *.test.ts             vitest unit tests
examples/playground/    Vite dev app (kitchen-sink demo)

License

MIT.

About

gene tree React component

Resources

Contributing

Stars

0 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages