Skip to main content

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 DetailView and loadOmeZarr) 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/t selections). See the SpatialCanvas JSDoc 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 Viv DetailView (orthographic pan/zoom), not OrbitView or volume rendering. You can select an image z slice (or c / t) through LayerConfig.channels, but that is OME axis indexing — not 3D scene navigation. ViewState includes a ViewState3D type 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).
  • useLayerData prefers explicit LayerConfig.channels when 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() feeds PointsDataEngine (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/layers PointsLayer composite 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 / MultiscaleImageLayer because the current Viv prop schemas reject null callback defaults such as onHover, onClick, and onViewportLoad.
  • Viv interpolation prop typing also needs cleanup: current Viv layer defaults still type interpolation incorrectly 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.

Recently landed (check repo for details)

  • Resource Resolver + reconcile loop (ADR 0004). @spatialdata/core defines the ResourceResolver interface, Resolution<T>, SpatialEntryError / EntryNotice, and a kind-blind SpatialEntryStore reconcile loop. useLayerData now consumes per-kind resolvers — PointsResolver / ShapesResolver in core, ImagesResolver / LabelsResolver in vis — through a single store.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/layersSpatialLayer + Zod SpatialLayerProps + migrateSpatialLayerProps (sublayer wiring still to grow).
  • Feature/table split@spatialdata/core now exposes a FeatureTableAlignment resolver shape, and @spatialdata/layers owns the reusable shape column-to-colour encoder. SpatialCanvas consumes 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 ImageView once 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 tooltipFields invalidate 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 SpatialLayer sublayers (image, scatter, shapes, …) and keep SpatialLayerProps migrations honest as kinds grow.
  • Harden @spatialdata/avivatorish for 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/core vs 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).