render_rerun

render_rerun class

Reactively render a rerun viewer.

Usage Source

render_rerun(
    _fn: Optional[ValueFn[ValueT]] = None,
    *,
    width: Optional[str] = None,
    height: Optional[str] = None,
    fill: Optional[bool] = None,
    fillable: Optional[bool] = None,
    rate_policy: Optional[RatePolicy] = None,
    rate_delay: Optional[float] = None,
)

rerun is a multimodal viewer for time-indexed data: you log 3D scenes, images, tensors, timeseries, or text logs from Python, and the viewer lets you scrub a timeline to see how it all evolved. Data logged to the viewer’s recording streams live to the browser, both during the initial render and from later reactive updates.

@render_rerun is a decorator that creates a render function – a function that returns the viewer to display and re-runs when the reactive values it reads change:

import rerun as rr
from rerun.notebook import Viewer

from shinywidgets import render_rerun


@render_rerun
def viewer():
    recording = rr.RecordingStream("my_app")
    recording.log("points", rr.Points2D([[0, 0], [1, 1]]))
    return Viewer(recording=recording)

The render function may return either a rerun.notebook.Viewer (the recommended, high-level API; it is coerced to its inner widget) or a rerun_notebook.Viewer widget directly. Both require pip install 'shinywidgets[rerun]'.

.. important:: Create the rerun.RecordingStream inside the render function and pass it to the viewer (Viewer(recording=...)), so each session gets its own recording. rerun’s usual rr.init() + rr.log() pattern writes to a single process-global recording. That’s fine in Jupyter, where one kernel serves one person, but a Shiny process serves every browser session at once – with a global recording, one user’s data would appear in every other user’s viewer.

Note that rr.notebook_show() does not work in Shiny: it renders through Jupyter’s display machinery (which Shiny never invokes), and it drains the recording, producing a one-shot snapshot with no live updates. Always construct a rerun.notebook.Viewer and return it from the render function instead – the viewer then stays connected, and data logged later streams in live.

Note

Accessing the rendered widget: the render function gains two read-only attributes: .value, the value it most recently returned, and .widget, the rendered rerun_notebook.Viewer widget. Since .widget is the low-level widget (not the rerun.notebook.Viewer wrapper), keep a reference to the wrapper if you want to call its methods (update_panels(), set_time_ctrl(), send_table(), …) from later reactive updates.

Responding to user interaction: viewer.selected() reads the viewer’s current selection, much like input.my_slider() reads an input. Call it in a reactive function (a @render.* function, @reactive.calc, or @reactive.effect), which then re-runs whenever the selection changes. It returns None until the user first selects something: the viewer’s empty load-time report is ignored, so a re-render keeps the last selection (its items then refer to the previous recording) rather than being replaced by the new viewer’s load-time report. A later empty report is a real deselection and is published.

For play/pause, timeline scrubbing, and other interactions (which have no accessor), register a callback with rerun.notebook.Viewer.on_event() and set a shiny.reactive.Value inside it. These interactions reach Python as one-off messages, not as synced widget state, so reactive_read() can’t see them – it only reads synced traits. rerun keeps high-frequency interactions off the synced-trait channel by design (syncing every timeline tick would be expensive), and the explicit callback lets you choose which events are worth reacting to:

from shiny import reactive

playing = reactive.Value(False)


@render_rerun
def viewer():
    rec = rr.RecordingStream(application_id="my_app")
    v = rr.notebook.Viewer(recording=rec)
    v.on_event(lambda evt: playing.set(evt.type == "play"))
    return v

Mutating the widget: Python-side trait updates on .widget (e.g., widget._width = 800) stream to the browser, as does data logged to the viewer’s recording after the render (see above).

In a filling layout, the viewer fills its container when its width/height are left at their defaults (or set to "auto"); an explicit integer width=/height= is respected instead. An explicit output_widget(height=...) also fits the default-sized viewer to the output’s height. The viewer’s large JavaScript/Wasm bundle loads from rerun’s CDN by default; set the RERUN_NOTEBOOK_ASSET environment variable to serve-local or inline to serve it from the Shiny process instead (see the rerun-notebook docs). Note that serve-local only works when the browser runs on the same host as the Shiny process (e.g., local development). For remote deployments, use inline instead, unless the asset server is proxied to the client.

Parameters

width : Optional[str] = None

A CSS length unit (e.g., "400px" or "100%") for the output’s width.

height : Optional[str] = None

A CSS length unit for the output’s height. When None (the default), the output is designed to fill the height of its parent container (see fill and fillable).

fill : Optional[bool] = None

Whether the output should grow or shrink to fit its parent container. When None (the default), this is True if height is not specified.

fillable : Optional[bool] = None

Whether the output container should fill available space (scrolling its contents on overflow). When None (the default), this is True if height is not specified.

rate_policy : Optional[RatePolicy] = None

How to rate-limit reactive_read() on the widget: "debounce" or "throttle". None (the default) debounces reactive_read() only if rate_delay is given; no accessor on this renderer declares a default.

rate_delay : Optional[float] = None

The rate-limit window in seconds (default 0.25). 0 disables rate limiting.

Examples

import numpy as np
import rerun as rr
from rerun.notebook import Viewer
from shiny.express import ui
from shinywidgets import render_rerun


@render_rerun
def viewer():
    rec = rr.RecordingStream(application_id="my_app")
    rec.log("points", rr.Points3D(np.random.rand(100, 3)))
    return Viewer(width="auto", height="auto", recording=rec)

Each accessor can also be read by name – out.read("selected") – which also accepts the widget’s bare synced-trait names.

See also

output_widget(): in Shiny Core, the UI needs this to hold the output (Shiny Express displays it where the render function is defined).

Methods

selected() The viewer’s most recently reported selection, as a rerun.notebook.SelectionChangeEvent. Its .items is a list of EntitySelectionItem (with the selected entity_path), ViewSelectionItem, and ContainerSelectionItem objects; an empty list means nothing is selected.

selected()

render_rerun.selected()

The viewer’s most recently reported selection, as a rerun.notebook.SelectionChangeEvent. Its .items is a list of EntitySelectionItem (with the selected entity_path), ViewSelectionItem, and ContainerSelectionItem objects; an empty list means nothing is selected.

Returns None until the user first selects something: the viewer’s empty selection at load is ignored, so a selection is kept across re-renders (a kept selection’s items refer to the previous recording). An empty report after that is a real deselection and is published. Use req(viewer.selected()) to wait for a selection. Selecting the same thing twice doesn’t fire twice. Not rate-limited.