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 render through a data engine, not a raw scatter path:
PointsElement.loadPoints()feedsPointsDataEngine(in@spatialdata/layers, with its cache/lifecycle half —PointsResolver— in@spatialdata/core), which owns a resident preload window with a configurable memory cap, a full-dataset feature catalog, per-row feature codes, and a whole-dataset feature-index scan that streams matching points in progressively. Rendering is via the@spatialdata/layersPointsLayercomposite with table-backed feature state (filter and colour by feature). Still open: a GeoArrow-backed fast path, and viewport/tile-based loading for points (shapes and points tiling are marked Future in ADR 0002/0003).
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
Recently landed (check repo for details)
- Resource Resolver + reconcile loop (ADR 0004).
@spatialdata/coredefines theResourceResolverinterface,Resolution<T>,SpatialEntryError/EntryNotice, and a kind-blindSpatialEntryStorereconcile loop.useLayerDatanow consumes per-kind resolvers —PointsResolver/ShapesResolverincore,ImagesResolver/LabelsResolverinvis— through a singlestore.reconcile()commit-effect instead of a per-kind load switch, with its 17-member public surface unchanged. See ADR 0004 and the resolver handoff / Step 1 consumption plans; Step 2 (points state model, shapes loader seam, memory) and Step 3 (renderer-adapter cleanup) remain. @spatialdata/avivatorish— shared loaders and Zustand stores; SpatialCanvas / ImageView import from the package.@spatialdata/layers—SpatialLayer+ ZodSpatialLayerProps+migrateSpatialLayerProps(sublayer wiring still to grow).- Feature/table split —
@spatialdata/corenow exposes aFeatureTableAlignmentresolver shape, and@spatialdata/layersowns the reusable shape column-to-colour encoder.SpatialCanvasconsumes these helpers rather than owning the fill-colour association rule. - SpatialCanvas — loader registry context (
VivLoaderRegistryProvider/useVivLoaderRegistry), layers / properties sidebars, DnD layer order, fullscreen toggle, channel controls for image layers. - Demo / Sketch — full-height shell for the vis demo.
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).
- Fold or replace
ImageViewonce SpatialCanvas covers the single-image case well. - 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
- Flesh out
SpatialLayersublayers (image, scatter, shapes, …) and keepSpatialLayerPropsmigrations honest as kinds grow. - Harden
@spatialdata/avivatorishfor MDV adoption (telemetry hooks, docs). - MDV integration checklist: replace vendored avivatorish, adopt shared layers, scatter/table-backed props, phased contour extraction.
- Points parity with shapes: move point rendering into
@spatialdata/layers, add stable point ids plus hide/fade/color/radius feature state, and make multi-file Parquet stores render progressively before adopting GeoArrow as a fast-path adapter. - 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 and docs track spatialdata 0.7.2 alongside older versions.
- NGFF unit schemas and axis-aware translation/scale transforms live in
@spatialdata/core(see Transformations).