Introduction
This project is a TypeScript implementation of the SpatialData library.
It is a collaborative effort by the Taylor CCB Group at the Center for Human Genetics, Oxford University and the Gihlenborg Group at the Harvard Medical School - Department of Biomedical Informatics.
The approach taken to the design is to follow the structure of the original library, adapting it to TypeScript with the intention that it should be familiar to users of the original library and aligned with best practices in frontend development.
There are several packages that aim to facilitate the use and development of this functionality:
Package map
| Package | Role | React? |
|---|---|---|
zarrextra | Lower-level zarr metadata helpers used by core | No |
@spatialdata/core | Load SpatialData Zarr stores; elements, transforms, table association | No |
@spatialdata/react | Thin hooks around core (useSpatialData, provider) | Yes |
@spatialdata/layers | deck.gl layers, shape/point renderers, versioned layer props | No |
@spatialdata/avivatorish | Viv loaders, channel stats, image Zustand stores | No |
@spatialdata/vis | SpatialCanvas UI + headless SpatialCanvasViewer | Yes |
These packages are published to npm:
pnpm add @spatialdata/vis
Clone the monorepo or pnpm pack workspace packages for local integration
while preparing release candidates (see Headless viewer guide).
@spatialdata/core
Vanilla-JS library for loading SpatialData Zarr stores. Mirrors the Python
spatialdata API where practical: elements, coordinate systems, lazy data
loads, and table–feature association (region / region_key / instance_key).
Built on zarrita for Zarr I/O and anndata.js for AnnData-backed
tables. The obs-column helpers (loadObsColumns, association resolvers) use a
direct loader path; other AnnData views are reached through getAnnDataJS().
See Core Package documentation:
- Element Classes — images, shapes, labels, points, tables
- Loading points and shapes — memory caps, streaming, tiled stores
- Transformations — coordinate systems and spatial transforms
- Error Handling — the
Resulttype pattern - Resource Resolver — cached loading with typed errors and notices
- Internals — architecture (for contributors)
Core-only example
import {
readZarr,
createFeatureTableAlignment,
loadFeatureRowIndexByFeatureIndex,
} from '@spatialdata/core';
const sdata = await readZarr('https://example.com/data.zarr');
// Load shape geometry + stable feature ids (async per element)
const shapesEl = sdata.shapes.cell_shapes;
const renderData = await shapesEl.loadRenderData();
// Join shapes to an associated AnnData table row index
const rowIndexByFeatureIndex = await loadFeatureRowIndexByFeatureIndex({
spatialData: sdata,
kind: 'shapes',
key: 'cell_shapes',
featureIds: renderData.featureIds,
});
const alignment = createFeatureTableAlignment({ rowIndexByFeatureIndex });
// alignment.resolveRowIndex({ featureId, featureIndex }) → table row or undefined
@spatialdata/layers
deck.gl–native, React-free rendering package. This is the layer between
core data and any viewer (custom DeckGL, MDV, Vitessce, or @spatialdata/vis).
What it owns today:
createShapesDeckLayer/buildShapesPrebuiltData— polygon, circle, and GeoArrow-table shape paths with feature-state styling (hide, fade, per-id colours)buildShapeFillColorByFeatureId— table column → per-feature colours (consumes core row alignment; does not resolve associations itself)SpatialLayer+spatialLayerPropsSchema/migrateSpatialLayerProps— versioned, JSON-serializable composite-layer contract (renders shapes sublayers)LabelsLayer,PointsLayer/PointsDataEngine— labels and points renderers- Pick/tooltip helpers keyed by stable
featureId
@spatialdata/vis calls into @spatialdata/layers for shapes, labels and points; MDV can drive
the same contracts by updating layer config / feature state without importing vis
internals.
React-agnostic deck example
import { createShapesDeckLayer, buildShapesPrebuiltData } from '@spatialdata/layers';
import { Deck } from '@deck.gl/core';
// renderData from core (see above)
const prebuilt = buildShapesPrebuiltData(renderData);
const shapesLayer = createShapesDeckLayer(
renderData,
{
kind: 'shapes',
elementKey: 'cell_shapes',
visible: true,
defaultFillColor: [70, 130, 180, 200],
featureState: { hiddenFeatureIds: ['42'] },
},
{ id: 'my-shapes', prebuilt }
);
const deck = new Deck({
canvas: document.getElementById('deck-canvas'),
initialViewState: { longitude: 0, latitude: 0, zoom: 1 },
layers: shapesLayer ? [shapesLayer] : [],
});
@spatialdata/react
Minimal React hooks for SpatialData: SpatialDataProvider + useSpatialData
wrap readZarr and expose { spatialData, loading, error }. No deck/Viv
dependencies — use with @spatialdata/vis or your own renderer.
@spatialdata/vis
React components and the SpatialCanvas stack (Viv images + deck vectors).
2D only today — orthographic pan/zoom via Viv DetailView; no 3D orbit or volume rendering.
Two entry modes:
- Batteries included —
SpatialCanvaswith coordinate-system picker, layer list, properties panels, tooltips. - Headless / controlled —
SpatialCanvasVieweroruseSpatialCanvasRenderer: same render path, your state forrenderStack.entriesandviewState, plus host overlay descriptors resolved by your app at runtime.
The vis package carries the most moving parts because of the Viv/deck/luma dependency stack.
- Headless viewer guide — practical guide for controlled / MDV-style embedding
- SpatialCanvas status and roadmap — what works, known gaps
- Visualization overview — package topology (
vis,layers,avivatorish) - MDV integration — phased MDV/Vitessce alignment
- MDV release checklist — branch shipping criteria
Headless vis example
import { SpatialCanvasViewer, type RenderStack } from '@spatialdata/vis';
const renderStack: RenderStack = {
schemaVersion: 1,
entries: [
{ kind: 'spatial', id: 'image', source: { elementType: 'image', elementKey: 'morphology' } },
{ kind: 'host', id: 'deck:selection', source: { hostLayerId: 'deck:selection' } },
{ kind: 'spatial', id: 'shapes', source: { elementType: 'shapes', elementKey: 'cells' } },
],
};
<SpatialCanvasViewer
spatialData={spatialData}
coordinateSystem="global"
renderStack={renderStack}
viewState={viewState}
onViewStateChange={setViewState}
hostLayerResolver={(entry) =>
entry.source.hostLayerId === 'deck:selection' ? mySelectionLayer : undefined
}
renderTooltip={false}
onFeatureHover={handleFeatureHover}
/>
Full UI example
import { SpatialCanvas } from '@spatialdata/vis';
<SpatialCanvas spatialData={spatialData} />
SpatialCanvas manages its own zustand store (layer visibility, order, channel
UI). Use it for demos and exploration; MDV and other hosts should prefer
SpatialCanvasViewer with externally owned state.