PrivatescenePrivateanimationPrivateperformancePrivateinputPrivaterenderingPrivateadaptivePrivateresolutionPrivate OptionaldatasetPrivate OptionalscalePrivate OptionalcolormapPrivate OptionalrecordingPrivate OptionallayersPrivate OptionalcontrolPrivate OptionalaudioPrivate OptionaloverlayPrivate OptionalpickingPrivate OptionallabelPrivate OptionalimagePrivate OptionalkeyReads the per-element keys CSR — a LabelLoader on the 'keys' channel (#1917).
PrivatepickingPer-init EventGroup for picking-system listeners (canvas mousemove, window resize, controls/scene-manager subscriptions). Re-disposed and rebuilt on each scene load.
PrivateisPrivateisTrue from the start of init() until routing and app-level wiring complete.
PrivateisRe-entrance guard for dispose. Set while a dispose is in flight
so a beforeunload callback that fires mid-dispose (or any nested call)
is a no-op rather than running the teardown a second time.
PrivateisIdempotency guard for dispose. Once teardown completes, further
dispose() calls are no-ops — fields still reference disposed instances,
so without this flag we would invoke dispose() on already-disposed
components (potentially double-freeing GPU resources). Reset by init().
PrivateeventsApp-level event listeners (beforeunload, focus, visibilitychange, luxar-open-dataset-browser, plus the picking system's mousemove + control change subscriptions). Disposed in one call from dispose.
PrivateoptionsSnapshot of init-time options. Populated by init() and read by
setupDebugInterface, dataset-browser callbacks, and other components
that need URL-derived flags without re-reading window.location.
Definitely-assigned: every method that reads this.options runs after
init(), which assigns the field as its first action.
PrivateembedderPer-app emitter for the public embedder events (on). Deliberately
a per-instance bus (not the global eventBus singleton) so the embedder
surface stays decoupled from the internal frame/UI plumbing and is
multi-instance-ready.
Private OptionaldatasetPrivate OptionaldatasetPrivate OptionalcurrentPrivate OptionalcameraSmooth camera transitions for flyTo; created in setupEmbedderHooks.
Private OptionalwaypointDims-manager listener driving the loaded scene's authored story waypoints
(viewer_config.waypoints); the driver itself lives in its closure.
Private OptionalwaypointPrivate OptionalcontrolRemote-control channel, present only when options.control is set.
PrivatecontrolThe scene's authored control-panel block, as last loaded.
Held for getViewerState() alone: the touch panel is a separate page and
cannot read the store's attributes itself. Reset on every dataset load, so
switching scenes cannot leave the previous scene's panel authoring behind.
PrivatekioskTeardown for the kiosk watchdog, if one is running.
Held so switchDataset cannot leave the previous scene's watchdog
listening on a canvas whose context state it no longer describes.
Private OptionalresizeObserves the canvas box so the viewer re-fits when the host container resizes (not just the window). Disconnected on dispose via events.
Private OptionalswitchIn-flight guard for every post-init dataset switch, whether requested by
the public switchDataset API or the built-in dataset browser.
loadDataset does a full teardown+reload, so overlapping switches would
corrupt scene state.
Get application components (for testing or advanced usage)
Get initialization state
Initialize the complete Luxar application.
Sets up the complete visualization pipeline including:
The initialization sequence is carefully ordered to ensure the animation loop starts BEFORE data loading, providing visual feedback even during long load operations.
Init-time options. URL parameters are not consulted here — main.ts is responsible for reading them and passing the result.
Promise that resolves when initialization is complete and dataset loading has started (may still be loading in background). Does NOT wait for all chunks to load.
const app = new LuxarApp();
await app.init({
canvas: document.getElementById('app') as HTMLCanvasElement,
src: 'https://example.com/cells.zarr',
});
PrivateshouldCheck if we should show the dataset browser
PrivateshowShow the dataset browser UI
PrivateloadLoad a dataset and initialize UI
PrivatesetupWire the programmatic-embedder hooks: re-emit dimension changes as the
public dimensions-changed event, and auto-resize to the canvas box via
a ResizeObserver. Both are guarded by isInitialized so they no-op
outside the live window, and both are torn down through events.
PrivateopenOpen the data-loading monitor in expanded mode on the Cache tab. Best-effort: silently skips when no monitor was created (e.g. embedded contexts that disable the monitor).
PrivateapplyApply zarr viewer_config state that isn't handled by RenderingControls.
RenderingControls handles rendering settings (bloom, AA, tone mapping, etc.). This method handles everything else: UI panel visibility, theme, dimension navigation state, and animation state.
Only explicitly set fields (not undefined) are applied — unset fields preserve the viewer's built-in defaults.
PrivateapplyLock the display down when the scene or the URL asks for it.
Resolved from BOTH the authored ui.kiosk block and ?kiosk, with the URL
winning — see config/kiosk.ts. Re-applied on every dataset load, and the
previous watchdog torn down first, so switching scenes cannot accumulate
listeners on a long-running exhibit.
PrivateinstallBind the sound layer to the loaded scene: apply the authored
viewer_config.audio defaults (the listener's persisted mute / master gain
win), then hand the engine the scene root so it finds the sound-node
placeholders loadSoundNode attached and starts decoding their clips.
PrivateinstallBind the scene's authored story waypoints to the dims manager: match once
now (snap — the opening framing, ahead of the plain camera block), then
fly whenever a dimension change makes a different waypoint match. Each
port is a piece the app already owns; the driver only sequences them.
PrivatedisposePrivateinstallAttach the remote-control channel, when one was asked for.
Built before initial dataset routing because its eager selection and
element listeners must exist when picking is provisioned. this.events
owns the teardown (see CONTROL_FORWARDED_EVENTS).
invoke indexes the app by method name, which is safe precisely because
isControlMethodAllowed has already vetted the name against a list the
lock test keeps exhaustive.
PrivateapplyPrivateinitInitialize or recreate the scale bar overlay. Creates a ScaleBar component and registers a per-frame callback to update it as the camera moves.
PrivateinitInitialize the colormap legend overlay. Shows per-layer colormap gradients with names and data ranges.
PrivatedisposeTear down the current OverlayManager, removing its DOM elements.
PrivateinitInitialize screen-space overlays from zarr metadata.
Always constructs an OverlayManager (even when the scene declares
no overlays) so the rest of the app — input handler, recording panel,
__luxarDebug.getOverlayManager() probe — sees a stable, non-null
collaborator. The manager just stays empty until loadOverlays (or a
runtime caller, e.g. a test) populates it.
PrivateinitInitialize GPU picking for hover tooltips, element actions, and embedder listeners. Activates for labels, image labels, keys, link/copy templates, or pick consumers. Wires PickingSystem to the label/key loaders, ImageLabelLoader, and OverlayManager.
PrivatedisposeTear down the current picking session (listeners, system, loaders).
Called by loadDataset UP-FRONT — alongside disposeOverlays, in
lockstep with clearSceneContent() — so a failing mid-load never
leaves a stale session firing picks against disposed geometries.
initPicking at the end of the load is the (re)creation point;
between the two no pick can fire (all listeners are removed here).
PrivatesetupRegister a beforeunload handler that disposes the app on page unload.
PrivatesetupSetup keyboard shortcut for opening dataset browser
PrivatesetupSetup window focus handling to trigger render on focus This prevents stale renders when switching between windows/tabs
PrivatesetupAuto-retry failed loaders when connectivity is restored — the trigger
for SceneLoader.retryAllFailedLoaders (whose doc names "after
connectivity is restored" as the intended use). Live accessor through
the manager so dataset switches keep pointing at the current loader.
PrivatesetupSetup debug interface for testing and AI-assisted development
This extends the existing debug interface (seeded by bootstrapStandalone() before init() runs) with runtime components that are only available after initialization:
Preserves existing properties (app, consoleInterceptor, version) from the bootstrap-side seeding.
Only enabled when LuxarAppOptions.debug is set (the standalone bootstrap
derives that from the ?debug URL param or persisted luxar.debug flag).
Capture a JSON-serialisable snapshot of the current viewer state.
Includes camera placement (position, target, up, projection params)
and per-dimension slice positions. Layer-panel state and rendering-
controls settings are not included in v1 — see
src/core/app/snapshot/viewer-snapshot.ts for the rationale and the schema.
Use the returned object to share a view, write a regression fixture, or hand to restoreSnapshot on another LuxarApp instance.
Restore viewer state from a snapshot produced by captureSnapshot.
Returns which parts of the snapshot were applied. Camera always applies
if the version matches; dims apply only when the snapshot's ndim
matches the loaded dataset (otherwise skipped with a warning rather
than throwing — common for cross-dataset link sharing).
Register a custom keyboard-routing context.
Remove a custom keyboard-routing context and all of its bindings.
Register a keyboard binding in a built-in or custom context.
Remove a keyboard binding. Missing bindings are ignored.
Optionalmodifiers: { ctrl?: boolean; shift?: boolean; alt?: boolean; meta?: boolean }Restore the context active before the latest pushContext.
Enable or disable all viewer keyboard shortcuts.
Resolve the active chord label for a registered action, if available.
PrivaterequireSubscribe to a public embedder event. Returns an unsubscribe function.
Events: dataset-loaded, dataset-error, dataset-fault, dimensions-changed,
selection (see LuxarEmbedderEventMap). Safe to call before
init(); the per-app emitter outlives individual init/dispose cycles.
Load a different dataset into the running viewer, reusing the full
teardown+reload path (the same one the built-in dataset browser uses).
Resolves when the new scene is loaded; emits dataset-loaded /
dataset-error.
Rejects if a switch is already in progress (the reload does a full scene teardown — overlapping calls would corrupt state).
Current nD dimension state (metadata + current slice positions + ranges). All array fields are cloned — safe to read without mutating internals; use setDimensionValue to change a slice.
Current latched fault episode, or null when no dataset is loaded or the latch is clear.
Set the slice position of a single (non-displayed) dimension. Clamped and
quantized by the scene-dims manager; triggers a data update and emits
dimensions-changed. Await awaitDimensionUpdate for the load.
Resolve once any in-flight dimension data update has settled.
Current camera pose (position, target, up, projection params) — the same
camera block captureSnapshot produces.
Apply a camera pose previously obtained from getCameraPose (or a
snapshot's camera). Controls are re-initialised so orbit/fly updates
don't snap back.
Fly the camera smoothly to pose (the same shape getCameraPose
returns). Interpolates in the orbit parameterisation — focus target,
viewing direction, distance, up — so the transition arcs around the
scene instead of cutting through it, and lands on pose exactly.
Any user input on the canvas or keyboard cancels the flight where it is,
as does a newer flyTo() or a dataset switch; the promise then resolves
{ completed: false }. durationMs: 0 is equivalent to
setCameraPose.
Optionalopts: FlyToOptionsCopy of the live rendering settings (tone mapping, exposure, bloom,
anti-aliasing, navigation feel, …) — the RenderingSettings object the
Rendering panel edits.
Apply a partial rendering-settings override. Takes the same path an
authored viewer_config takes at load (validation, camera FOV / planes,
navigation, post-processing), so anything an author can bake a
controller can set live. Values are validated and clamped; nothing is
persisted to the user's stored preferences.
Per-layer appearance summaries in Layers-panel order (copies). Empty before a scene has loaded.
Patch one layer's appearance (visibility, opacity, display window, gamma,
colormap, blending mode, absorption, order). Each field takes the same
route the Layers panel's own control does. path is the
LayerSummary.path of a layer from getLayers.
One-call mirror of everything a remote controller needs: dataset, camera, slice position, rendering settings, layers. All copies.
The sound layer's state: AudioContext state (suspended = the display
still needs its tap), mute, master gain, panning model, bus gains, the
names of the nodes playing, and whether the scene has sound nodes at all.
Live mixer patch: master gain and mute (both persisted like the rail's own controls), per-bus gains, panning model. Fields left out are untouched.
Render the current frame to an encoded image Blob (PNG by default). Async because the WebGPU readback path is async.
Optionalopts: ScreenshotOptionsDispose all application resources.
Tears down the animation loop, scene, input handlers, UI panels, and registered listeners. Idempotent: safe to call repeatedly. After dispose(), the LuxarApp instance is in an uninitialized state — call init() again to re-create resources, or discard the instance.
PrivategetGet visibility states of all UI panels for save/restore during recording.
Implementation lives in core/app/viewer-config/panel-visibility.ts.
PrivaterestoreRestore UI panel visibility from a saved state map.
The sound layer; created by the init pipeline, nodes attach per scene.