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? | npm status |
|---|---|---|---|
zarrextra | Lower-level zarr metadata helpers used by core | No | Alpha |
@spatialdata/core | Load SpatialData Zarr stores; elements, transforms, table association | No | Alpha |
@spatialdata/react | Thin hooks around core (useSpatialData, provider) | Yes | Alpha |
@spatialdata/layers | deck.gl layers, shape/point renderers, versioned layer props | No | Alpha |
@spatialdata/avivatorish | Viv loaders, channel stats, image Zustand stores | No | Alpha |
@spatialdata/vis | SpatialCanvas UI + headless SpatialCanvasViewer | Yes | Alpha |
These packages are published to npm under the default latest tag. The API is
still alpha, so expect breaking changes between minor versions:
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. Table obs-column helpers used today (loadObsColumns, association
resolvers) sit on a direct loader path; the roadmap is a coherent
TableElement → AnnData.js / zarrita DataLoader surface for obs,
var, X, obsm, and related AnnData views without every integrator calling
getAnnDataJS() directly.
See Core Package documentation:
- Element Classes — images, shapes, labels, points, tables
- Transformations — coordinate systems and spatial transforms
- Error Handling — the
Resulttype pattern - 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 (image/points sublayers still maturing)- Pick/tooltip helpers keyed by stable
featureId
@spatialdata/vis calls into @spatialdata/layers for shapes; 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 is more experimental than core/layers because of the
Viv/deck/luma dependency stack. The near-term goal is an MDV-consumable
prerelease with headless embedding first; API stability guarantees come after
that smoke test.
- 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.