Skip to content

Architecture ​

Principles ​

Each one traces to a lesson from an earlier viewer:

  • One GL context for all samples (ABC Atlas slide view, Odon mosaic). Stepping samples moves nothing in memory.
  • Recolor is a one-byte-per-cell upload. Legend toggles, colormap, range and filter bounds are uniform or 4 KB texture updates. Nothing is refetched.
  • Loaders open once per sample and are cached. Switching samples or colors never rebuilds them.
  • Decoding runs in a worker. The main thread receives transferred ArrayBuffers.
  • Rows are shuffled at build time, so level of detail is a prefix draw count rather than a second attribute.
  • React renders chrome only. The canvas is driven by store subscriptions.

App modules ​

app/src
  views/ViewerController.ts   deck.gl Deck, two OrthographicViews, camera, store subscription, diagnostics
  views/loader.ts             manifest + sample opening + the arrays the current state needs
  views/layers.ts             store state → deck.gl layers (points, images, cell shapes, frames, labels)
  views/tileManager.ts        spatial tiles per sample: index, fetch once the camera settles, cache, generation across datasets
  views/shapes.ts             cell shape tiles per pane: gate and budgets, colour bytes gathered from the cell arrays
  views/molecules.ts          molecule tiles per pane: the density gate and the per-gene colours
  layers/MoleculeLayer.ts     a point per molecule, coloured through a per-gene texture
  layers/colorModule.ts       the cell colour rule shared by dots, filled shapes and shape edges
  layers/ShapeLayers.ts       ShapeFillLayer (worker-triangulated) and ShapeEdgeLayer
  views/interaction.ts        pointer + keyboard: hover lookup and tooltip rows, click, lasso gestures, shortcuts
  views/selection.ts          lasso polygon → selected indices, highlight layers, summary, CSV
  views/stats.ts              category counts, filter histogram, gene-by-annotation table
  views/blend.ts              two-set gene scores
  layers/ExpressionScatterLayer.ts  ScatterplotLayer + 4 per-instance bytes (value, value2, category, filter),
                                    1D/2D LUT + palette textures, LOD prefix draw
  layers/imageLayers.ts       Viv MultiscaleImageLayer per sample image
  data/worker.ts              zarrita reads (sharded zarr v3 + zstd), KDBush build, shape tiles (coalesced ranges, decode, earcut)
  data/tileIndex.ts           tile index shared by shapes and molecules: tiles in view, range coalescing, byte planes
  data/shapeTiles.ts          shape tile decoder
  data/moleculeTiles.ts       molecule tile decoder
  data/cache.ts               main-thread caches keyed by sample: positions, index, obs arrays, gene LRU
  store/store.ts              Zustand store: small descriptors only, never typed arrays
  url/urlState.ts             URLSearchParams ⇄ store
  ui/*                        sidebar panels, legend, gallery, site bar, scale bar

Shader color modes ​

modemeaninginputs
0continuousinstanceValue → 256-entry LUT
1categoricalinstanceCategory → palette texture, alpha 0 = hidden
2not measuredconstant grey
3blendinstanceValue, instanceValue2 → 32×32 2D LUT

The filter channel (instanceFilter, uniform bounds) applies in every mode; filtered or hidden instances are moved outside clip space.

Data flow for a gene switch ​

  1. Store color changes → controller bumps a generation counter → Loader.loadChannels().
  2. For each open sample without the gene cached, the worker reads one Zarr chunk (one range request on sharded stores), decodes zstd, transfers a Uint8Array.
  3. The main-thread LRU stores it; LayerBuilder builds layers whose data.attributes.getValue points at the array. Positions keep the same descriptor object, so deck.gl does not re-upload them.
  4. deck.gl uploads the new byte attribute and redraws. Cached switches skip step 2 entirely.

Test hooks ​

In development and e2e builds, window.__sscape exposes ready, setGene(gene) → ms, getState(), pixelProbe(), the store and the controller.

MIT licensed.