Skip to main content

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

PackageRoleReact?npm status
zarrextraLower-level zarr metadata helpers used by coreNoAlpha
@spatialdata/coreLoad SpatialData Zarr stores; elements, transforms, table associationNoAlpha
@spatialdata/reactThin hooks around core (useSpatialData, provider)YesAlpha
@spatialdata/layersdeck.gl layers, shape/point renderers, versioned layer propsNoAlpha
@spatialdata/avivatorishViv loaders, channel stats, image Zustand storesNoAlpha
@spatialdata/visSpatialCanvas UI + headless SpatialCanvasViewerYesAlpha

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:

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.

See Layers package overview.

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:

  1. Batteries includedSpatialCanvas with coordinate-system picker, layer list, properties panels, tooltips.
  2. Headless / controlledSpatialCanvasViewer or useSpatialCanvasRenderer: same render path, your state for renderStack.entries and viewState, 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 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.