Custom React Surfaces

Compose your own panes and layers while React owns the canvas and engine lifecycle.

Navigate linked panes

Pan either pane or zoom with the wheel. Both share a Y window; their X windows stay independent.

Preparing view…
Pane A · Camera APane B · Camera B
const unlink = linkAxes([cameraA, cameraB], 'y')
// Call unlink() when the linked group is removed.

Use a custom surface for well tracks, linked panes or your own layer composition. React owns the canvas; your callbacks supply content.

Create the Surface

'use client'

import { Camera, PointsLayer } from '@bs/outkrop-core'
import { OutkropSurface } from '@bs/outkrop-react'

export function CustomPlot() {
  return (
    <OutkropSurface
      style={{ height: 480 }}
      setup={(engine) => {
        const pane = engine.addPane({
          id: 'plot',
          rect: { x: 0, y: 0, width: 1, height: 1 },
          camera: new Camera({ x: [0, 4], y: [0, 10], flipY: false }),
        })
        pane.add(new PointsLayer({ x: [1, 2, 3], y: [2, 8, 5], size: 10 }))
      }}
      layout={({ width, height }, engine) => {
        engine.pane('plot')?.setRect({ x: 0, y: 0, width, height })
      }}
      controls={{ mode: 'pan' }}
      fallback={<p>This browser cannot create a WebGL2 view.</p>}
    />
  )
}

layout replaces the initial rectangle before the first frame and runs after resizing. Sizes are CSS pixels. Reserve room for axes; give the surface a height.

Know What Runs When

sequenceDiagram
  accTitle: React surface lifecycle
  accDescr: On client mount the surface creates an engine and calls setup. Layout runs before drawing and on resize. On unmount the surface calls the setup cleanup and disposes the engine.
  participant React
  participant Surface as OutkropSurface
  participant App as Your callbacks
  React->>Surface: Mount on client
  Surface->>App: setup(engine)
  Surface->>App: layout(size, engine)
  Note over Surface: Draw when state or data changes
  Surface->>App: layout after resize
  React->>Surface: Unmount
  Surface->>App: Run setup cleanup
  Note over Surface: Dispose controls and engine
Change Behavior
Mount Create engine, run synchronous setup, then layout
Resize Run layout
Callback prop update Does not rerun setup; update layers through an effect
New key or pixelRatio Rebuild the engine and rerun setup
Unmount Run setup cleanup, dispose engine/controls/canvas

Setup must not be async. Load metadata first, or start cancellable work inside setup and return cleanup. Your app owns sources/workers and listener subscriptions it creates. StrictMode can replay effects.

Controls and Export

  • controls={{ mode: 'pan', wheel: 'y' }} pans and restricts wheel zoom to Y. Use 'zoom' for box selection or false to disable controls.
  • background uses premultiplied RGBA in [0, 1]; transparent is the default.
  • OutkropSurfaceHandle exposes engine, controls and exportPng(). Handles are not ready before mount; catch export failures.
  • HTML children sit above the canvas and are not part of its PNG.

Imports are SSR-safe; browser resources start after hydration. Next.js callback/hook components need a client boundary. See themes and hooks.