loom-browser
v0.0.11
Published
A modular, headless-first genomic visualization engine built in TypeScript
Maintainers
Readme
Loom renders genomic tracks (genes, signal, sequence, interactions) to Canvas or SVG. It works headlessly in Node for server-side rendering, in web workers, and in the browser with full interactivity.
Quick Start
npm install loom-browserInteractive browser (DOM)
import { GenomeBrowser } from 'loom-browser'
const browser = new GenomeBrowser(document.getElementById('container')!, {
locus: { chr: 'chr17', start: 7_668_000, end: 7_688_000 },
})
browser.addRuler()
browser.addGeneTrack() // UCSC RefSeq genes
browser.addWigTrack('https://example.com/signal.bigWig', {
config: { color: '#4A90D9', height: 80 },
})
browser.addBedTrack('https://example.com/peaks.bed.gz')
browser.addInteractionTrack('https://example.com/loops.bedpe')
browser.addSequenceTrack() // visible at base-level zoomBuilt-in drag-to-pan, sweep-to-zoom on the ruler, pinch-to-zoom, and responsive resizing — no setup needed.
Headless (Node / SSR)
import { HeadlessGenomeBrowser } from 'loom-browser'
const browser = new HeadlessGenomeBrowser({
locus: { chr: 'chr17', start: 7_668_000, end: 7_688_000 },
viewportWidth: 1200,
})
browser.addRuler()
browser.addGeneTrack()
browser.addWigTrack('https://example.com/signal.bigWig')
// Wait for data, then export
browser.on('dataloaded', () => {
const svg = browser.toSVG({ width: 1200, backdropColor: '#fff' })
fs.writeFileSync('output.svg', svg)
})UI Shell (web components)
Drop in a full browser shell with navbar, locus search, zoom controls, and export buttons:
import { createShell } from 'loom-browser'
// 5 lines to a fully working genome browser with ruler + gene tracks
const shell = createShell(document.getElementById('container')!, {
locus: { chr: 'chr17', start: 7_668_000, end: 7_688_000 },
shellTheme: 'dark', // 'classic' | 'modern' | 'dark' — auto-maps render theme
})
// Add more tracks as needed
shell.browser.addWigTrack('https://example.com/signal.bigWig')Or use the custom element directly:
<loom-browser theme="modern" id="my-browser"></loom-browser>React
Declarative React bindings are available via loom-browser/react. Tracks are JSX children that auto-add on mount and auto-remove on unmount. React 18+ is required but optional — if you don't import from loom-browser/react, no React code is included.
import { LoomBrowser, RulerTrack, WigTrack, GeneTrack } from 'loom-browser/react'
import { modernRenderTheme } from 'loom-browser'
function App() {
const [locus, setLocus] = useState({ chr: 'chr17', start: 7_668_000, end: 7_688_000 })
return (
<LoomBrowser locus={locus} onLocusChange={setLocus} theme={modernRenderTheme}
style={{ height: 400 }}>
<RulerTrack />
<WigTrack url="https://example.com/signal.bw" config={{ color: '#4A90D9' }} />
<GeneTrack />
</LoomBrowser>
)
}Hooks: useGenomeBrowser(), useBrowserEvent(event, handler), useLocus().
Track Types
| Type | Method | Formats | Description |
|------|--------|---------|-------------|
| Ruler | addRuler() | — | Genomic coordinate axis with tick marks |
| Gene / Annotation | addGeneTrack(), addBedTrack(url) | BED, GFF3, GTF, GenePred, RefFlat, NarrowPeak, BroadPeak | Gene models with exons, UTR, strand arrows, labels |
| Signal | addWigTrack(url) | BigWig, BedGraph | Bar, line, points, or dynamic-sequence graphs with autoscale |
| Sequence | addSequenceTrack() | UCSC API | DNA bases with optional 3-frame translation |
| Interaction | addInteractionTrack(url) | BEDPE, UCSC interact | Arc diagrams for chromatin loops and contacts |
Navigation
browser.search('TP53') // gene name lookup
browser.search('chr17:7,668,000-7,688,000') // locus string
browser.setLocus({ chr: 'chr17', start: 7_668_000, end: 7_688_000 })
browser.zoomIn() // 2x
browser.zoomOut(4) // 4xEvents
browser.on('locuschange', ({ locus }) => { /* navigation happened */ })
browser.on('dataloaded', ({ track }) => { /* track finished loading */ })
browser.on('trackclick', ({ track, feature, x, y }) => { /* feature clicked */ })
browser.on('trackcontextmenu', ({ track, feature, x, y }) => { /* right-click */ })Full list: locuschange, trackadded, trackremoved, trackorderchanged, dataloaded, dataerror, rendererror, trackclick, trackhover, trackcontextmenu, roiadded, roiremoved, roichanged, roiclick, roicontextmenu.
Themes
Three built-in render themes control colors for all track types:
import { defaultRenderTheme, modernRenderTheme, darkRenderTheme } from 'loom-browser'
const browser = new GenomeBrowser(container, {
locus,
theme: darkRenderTheme,
})Override individual properties or provide per-track overrides:
browser.addWigTrack(url, {
config: { color: '#E74C3C', graphType: 'line', height: 60 },
})Export
// SVG string
const svg = browser.toSVG({ width: 1200, backdropColor: '#ffffff' })
// PNG data URL (DOM browser only)
const png = await browser.toPNG()
// Download files
browser.saveSVGtoFile('my-region.svg')
await browser.savePNGtoFile('my-region.png')Regions of Interest (ROIs)
browser.addROI({
chr: 'chr17', start: 7_674_000, end: 7_676_000,
color: 'rgba(255, 0, 0, 0.15)',
label: 'Exon 7',
})
const rois = browser.getVisibleROIs()
browser.removeROI(rois[0].id)Sessions
Save and restore browser state:
const session = browser.toJSON() // serialize
browser.loadSession(session) // restore
// Import from igv.js session format
import { fromIgvSession } from 'loom-browser'
const loomSession = fromIgvSession(igvSessionObject)Remote Control (WebSocket)
Let an LLM or agent control the browser over WebSocket:
const ws = new WebSocket('wss://your-server/browser')
browser.attachRemote(ws)Or use CommandDispatcher in-process:
import { CommandDispatcher } from 'loom-browser'
const dispatcher = new CommandDispatcher(browser)
const state = await dispatcher.dispatch({ command: 'get_browser_state' })
await dispatcher.dispatch({ command: 'navigate', locus: 'BRCA1' })
await dispatcher.dispatch({
command: 'modify_tracks',
actions: [{ action: 'add', type: 'wig', url: 'https://example.com/signal.bigWig' }],
})Commands: get_browser_state, navigate, modify_tracks, query_features, set_layout, export_view, manage_rois, subscribe_events.
State Projection
Inspect browser state programmatically (useful for agents/LLMs):
const state = browser.state.getState()
// { locus, locusString, span, zoomLevel, tracks: [...], rois: [...] }
// Diff workflow
browser.state.getState({ record: 'before' })
browser.search('chr1:1,000,000-2,000,000')
const diff = browser.state.diff('before')
// { locus: { from, to }, zoomLevel: { from, to }, ... }Web Workers
Offload BigWig decoding and feature packing to a worker thread:
import { GenomeBrowser, WebWorkerProvider } from 'loom-browser'
const browser = new GenomeBrowser(container, {
locus,
workerProvider: new WebWorkerProvider('/loom-worker.js'),
})In Node:
import { NodeWorkerProvider } from 'loom-browser'
const browser = new HeadlessGenomeBrowser({
locus,
viewportWidth: 1200,
workerProvider: new NodeWorkerProvider(),
})Low-Level Renderer API
Use standalone renderers and track canvases directly — no browser instance needed:
import { AnnotationTrackCanvas, fetchGeneFeatures } from 'loom-browser'
const canvas = document.createElement('canvas')
const track = new AnnotationTrackCanvas(canvas, {
locus: { chr: 'chr17', start: 7_668_000, end: 7_688_000 },
features: [],
config: { displayMode: 'EXPANDED', featureHeight: 14, drawLabels: true },
})
track.attachTo(document.getElementById('track-container')!)
const features = await fetchGeneFeatures(track.locus)
track.setFeatures(features)Architecture
Loom uses a four-layer architecture. Each layer has zero dependencies on the layers above it:
- Data + Layout — Types, decoders, data sources, feature packing. No DOM, no canvas. Safe for Node and workers.
- Renderers — Stateless pure functions:
(ctx, features, config, viewport) → pixels. TakesCanvasRenderingContext2D, no DOM. - Headless Browser — Track orchestration, data lifecycle, navigation, session serialization, SVG export. No DOM.
- DOM Shell —
GenomeBrowseradds pointer events, drag/pan/zoom, axis columns, ResizeObserver. Web components for UI.
Development
git clone https://github.com/riyavsinha/loom.git
cd loom
npm install
npm run buildnpm test # unit tests (Vitest)
npm run typecheck # type check src/
npx tsc --noEmit # type check js/ (legacy)
npm run storybook # component stories
npm run test:visual # Playwright screenshot tests