SpatialCanvas and images — status and roadmap
@spatialdata/vis includes SpatialCanvas, a React UI for picking a coordinate system, toggling layers, and panning/zooming. It can render OME-Zarr images together with shapes and points in one deck.gl + Viv view.
Scope today: 2D only. There is no 3D scene rendering — no orbit navigation, pitch/bearing, or volume views. See Known limitations.
What works
- Combined canvas: Image layers (via Viv
DetailViewandloadOmeZarr) draw under vector layers; composition follows the same pattern as MDV’s Viv + deck stacking. - Coordinate system selection and layer visibility toggles from the SpatialData store.
- Advanced API: You can pass per-image channel settings on the spatial canvas store using
LayerConfig.channels(colors, contrast limits, channel visibility,z/c/tselections). See theSpatialCanvasJSDoc example in the source package.
Known limitations
- No 3D rendering:
SpatialCanvas,SpatialCanvasViewer, and the underlying Viv/deck stack are 2D Cartesian only. The viewer uses VivDetailView(orthographic pan/zoom), notOrbitViewor volume rendering. You can select an imagezslice (orc/t) throughLayerConfig.channels, but that is OME axis indexing — not 3D scene navigation.ViewStateincludes aViewState3Dtype stub, but pitch, bearing, and orbit state are not wired through to rendering. 3D is not supported for MDV v1 integration, but a high priority following that. - Channel UI is basic: the properties pane exposes per-channel RGB (0–255), contrast min/max,
z/c/t, and visibility (up to six channels). There is no histogram or brush yet; defaults from automatic stats can still look wrong until you tune values. - Single primary image path in the Viv viewer composition (first enabled image layer drives
DetailView.getLayers). useLayerDataprefers explicitLayerConfig.channelswhen arrays are non-empty; further override flags may still be useful for edge cases.- Points are loaded by core's
PointsResolverand drawn byPointsLayerthroughPointsDataEngine(both described in layers): a resident preload window under a memory cap, or viewport tiles for Morton-sorted and points-zarr elements, with a feature catalog and filter and colour by feature. See Loading points and shapes. A GeoArrow-backed fast path is still open. - Shapes load non-blocking: geometry refines an already-painted canvas.
Upstream Viv Follow-ups
- Labels currently carry local Viv workarounds: SpatialCanvas labels use local single-scale and multiscale wrappers instead of Viv
ImageLayer/MultiscaleImageLayerbecause the current Viv prop schemas rejectnullcallback defaults such asonHover,onClick, andonViewportLoad. - Viv interpolation prop typing also needs cleanup: current Viv layer defaults still type
interpolationincorrectly on some paths, which triggers deck.gl prop validation when labels request'nearest'/'linear'. - Shader path should converge back toward upstream Viv: labels already use GLSL 300 ES + UBO-style uniforms locally; once the Viv shader/toolchain work is published we should revisit these wrappers and delete any compatibility code that is no longer needed.
Related code
packages/vis/src/SpatialCanvas/index.tsx—SpatialCanvasUI and store wiringpackages/vis/src/SpatialCanvas/VivSpatialViewer.tsx— Viv + deck layer compositionpackages/vis/src/SpatialCanvas/useLayerData.ts— the Resource Resolver reconcile loop (SpatialEntryStore) and Viv layer propspackages/vis/src/SpatialCanvas/types.ts—LayerConfig,ChannelConfigpackages/core/src/tableAssociations.ts— feature/table row alignment following Pythonspatialdatasemanticspackages/layers/src/shapeColorEncoding.ts— renderer-agnostic shape column-to-colour encoding over resolved row alignment
Architecture
- Resource Resolver (ADR 0004).
useLayerDatadrives a kind-blindSpatialEntryStore.reconcile()over per-kind resolvers (PointsResolver/ShapesResolverin core,ImagesResolver/LabelsResolverin vis). See Resource Resolver and ADR 0004. Per-layer typed errors and notices surface asgetEntryStatus(headless guide). - Feature/table split. Core owns row alignment (
FeatureTableAlignment);@spatialdata/layersowns the column-to-colour encoders;SpatialCanvasconsumes both (details). @spatialdata/avivatorishholds the shared loaders and Zustand stores.- SpatialCanvas has a loader registry context (
VivLoaderRegistryProvider), layers and properties sidebars, drag-and-drop layer order, fullscreen, and channel controls for image layers.
Headless embedding
SpatialCanvasViewer and useSpatialCanvasRenderer are exported for
controlled, UI-free embedding (MDV, Vitessce, local experiments). See
Headless viewer guide.
Near-term roadmap
- Histogram (optional) and richer channel UX (MDV parity).
- Remove the deprecated
ImageView(#215). - Feature/table foundations: tooltip and pick-event row resolution now route
through the shared core resolver shape; remaining work is closing the per-element
tooltip cache ping-pong (two layers over one element with different
tooltipFieldsinvalidate each other — Track B) and adding annotation column discovery for direct shape annotations and future matrix-backed values. See Feature table associations and annotation columns.
Medium-term roadmap
- Grow
SpatialLayerbeyond shapes sublayers, keepingSpatialLayerPropsmigrations honest. - Harden
@spatialdata/avivatorishfor MDV adoption (telemetry hooks, docs). - MDV integration checklist: replace vendored avivatorish, adopt shared layers, scatter/table-backed props, phased contour extraction.
- GeoArrow / Parquet paths for shapes and points; clarify
@spatialdata/corevs deck-facing buffers. - 3D view mode and time (
t) in the public scene contract.
Core and fixtures
- Test fixtures cover spatialdata 0.5.0, 0.6.1, 0.7.2 and 0.8.0.
- NGFF unit schemas and axis-aware translation/scale transforms live in
@spatialdata/core(see Transformations).