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?
zarrextraLower-level zarr metadata helpers used by coreNo
@spatialdata/coreLoad SpatialData Zarr stores; elements, transforms, table associationNo
@spatialdata/reactThin hooks around core (useSpatialData, provider)Yes
@spatialdata/layersdeck.gl layers, shape/point renderers, versioned layer propsNo
@spatialdata/avivatorishViv loaders, channel stats, image Zustand storesNo
@spatialdata/visSpatialCanvas UI + headless SpatialCanvasViewerYes

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:

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.

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 included — SpatialCanvas with coordinate-system picker, layer list, properties panels, tooltips.
  2. Headless / controlled — SpatialCanvasViewer 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 carries the most moving parts because of the Viv/deck/luma dependency stack.

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.