An HTML report often needs one
interactive figure for both hemispheres and several result maps.
Repeating a widget for every map wastes space and sends the same mesh to
the browser many times. surface_scene() describes the
geometries, maps, labels, and fallback once; surfwidget()
displays that scene in R Markdown or Quarto; and
write_surface_scene() writes the same scene as an ordinary
web page.
This article builds one bilateral viewer with two selectable maps. The example uses package data, runs without a network connection, and keeps the scientific transformation in R.
A SurfaceScene has one SurfaceGeometry per
hemisphere and one or more named scalar layers. Each bilateral layer
supplies a left and right value vector whose lengths match the
corresponding geometry. A scene also records the initial map, legend
metadata, provenance, alternative text, and fallback text.
The example surfaces are small meshes shipped with neurosurf:
left_geometry <- read_surf(system.file(
"extdata", "std.8_lh.smoothwm.asc", package = "neurosurf"
))
right_geometry <- read_surf(system.file(
"extdata", "std.8_rh.smoothwm.asc", package = "neurosurf"
))The browser renders the values that R supplies. Perform thresholding,
capping, atlas projection, and other scientific transformations before
constructing a layer. Here we create a deterministic coordinate-based
demonstration map and replace sub-threshold values with NA
in R:
scaled_height <- function(geometry) {
value <- coords(geometry)[, 3]
3 * (value - mean(value)) / max(abs(value - mean(value)))
}
effect <- list(
left = scaled_height(left_geometry),
right = scaled_height(right_geometry)
)
effect <- lapply(effect, function(x) replace(x, abs(x) < 1.25, NA_real_))The second map is a simple two-region label used to demonstrate map switching:
Create each named map with surface_layer(), then put
both geometries and all layers in one scene. selected_layer
chooses the map shown when the viewer opens.
scene <- surface_scene(
left = left_geometry,
right = right_geometry,
layers = list(
surface_layer(
"effect", effect,
colormap = c("#2166ac", "#f7f7f7", "#b2182b"),
limits = c(-3, 3), units = "z",
legend = list(title = "Thresholded effect")
),
surface_layer(
"region", region,
colormap = c("#d9d9d9", "#238b45"),
limits = c(0, 1), units = "class",
legend = list(title = "Demonstration region")
)
),
selected_layer = "effect",
metadata = list(subject = "example"),
provenance = list(source = "neurosurf package data"),
fallback = "Static bilateral surface figure with effect and region maps.",
alt_text = paste(
"Bilateral cortical surfaces with selectable thresholded effect",
"and demonstration region maps."
),
preset = "paper-light",
mode = "report"
)Pass the scene to surfwidget() once. The map selector
changes the active values and legend without constructing another widget
or another WebGL context.
The report toolbar provides the authored map selector, coordinated anatomical views, reset, and PNG export. Drag to rotate, use the wheel or a pinch gesture to zoom, and right-drag to pan.
paper-light and report name different
contracts:
| Setting | Controls | What it changes |
|---|---|---|
preset = "paper-light" |
Appearance | Light background, restrained lighting and material values, labels, and PNG defaults. |
mode = "report" |
Behavior | Lazy mounting, the compact report toolbar, deterministic initial framing, and fallback/print behavior. |
mode = "viewer" |
Behavior | A bare rotatable viewer without the report toolbar. |
Tweakpane is deprecated. The report runtime does not load it or
contact a CDN. Legacy showControls and
controlType configuration entries warn during the
migration; use the scene mode instead.
Use write_surface_scene() when the viewer must live
outside an R Markdown or Quarto document. Both output modes bundle the
compatible surfviewjs runtime locally and make no network requests.
# index.html, the runtime, and content-addressed .bin files
write_surface_scene(scene, "surface-report", self_contained = FALSE)
# one index.html with the runtime and typed arrays inlined
write_surface_scene(scene, "surface-report-inline", self_contained = TRUE)Choose adjacent assets for a site or a report directory: the browser can cache the runtime and SHA-addressed arrays, and repeated assets are deduplicated. Choose a self-contained file when a single portable HTML file matters more than file size or cross-page caching.
The HTML writer produces a minimal page that uses the public
surfview.mountSurfView() API. You can customize its
generated index.html or use the same lifecycle in another
page. In this example, manifest is a
surfview.scene.v1 object produced by
surface_scene_manifest():
<div id="surface-report"></div>
<script src="./surfview.embed.iife.js"></script>
<script>
const handle = surfview.mountSurfView(
document.getElementById("surface-report"),
manifest,
{
lazy: true,
preset: "paper-light",
controls: true,
baseUrl: document.baseURI,
bilateralGroup: {
id: "bilateral",
leftSurfaceId: "left",
rightSurfaceId: "right"
}
}
);
handle.ready.then(() => handle.setView("lateral"));
window.addEventListener("pagehide", () => handle.dispose(), { once: true });
</script>A manifest with both hemispheres must name a
bilateralGroup that pairs the left and right surface ids;
without it the report runtime refuses to mount.
surfwidget() and write_surface_scene() supply
this option automatically.
The returned handle also provides selectLayer(),
resize(), exportPNG(), and
dispose(). Call dispose() when an application
removes or replaces the viewer so it can release observers, listeners,
animation frames, the WebGL context, and GPU resources.
surfwidget() still accepts SurfaceGeometry,
NeuroSurface, ColorMappedNeuroSurface, and
VertexColoredNeuroSurface. Those methods adapt a single
surface to a SurfaceScene and are useful for quick
exploration:
values <- scaled_height(left_geometry)
surface <- NeuroSurface(left_geometry, nodes(left_geometry), values)
surfwidget(surface, irange = c(-3, 3))Use an explicit SurfaceScene for bilateral figures,
multiple named maps, portable assets, authored fallback text,
provenance, or report behavior.
vignette("surface-figures") builds static,
publication-quality multi-view figures with
surface_figure().vignette("displaying-surfaces") covers RGL-based 3D
rendering, curvature shading, and PNG snapshots.vignette("introduction-to-neurosurf") introduces
SurfaceGeometry, NeuroSurface, and the
package’s other core data structures.