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 vMutating 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 (seefillandfillable). fill : Optional[bool] = None-
Whether the output should grow or shrink to fit its parent container. When
None(the default), this isTrueifheightis not specified. fillable : Optional[bool] = None-
Whether the output container should fill available space (scrolling its contents on overflow). When
None(the default), this isTrueifheightis not specified. rate_policy : Optional[RatePolicy] = None-
How to rate-limit
reactive_read()on the widget:"debounce"or"throttle".None(the default) debouncesreactive_read()only ifrate_delayis given; no accessor on this renderer declares a default. rate_delay : Optional[float] = None-
The rate-limit window in seconds (default 0.25).
0disables 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.