render_perspective

render_perspective class

Reactively render a perspective viewer.

Usage Source

render_perspective(
    _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,
)

@render_perspective 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 pandas as pd

from shinywidgets import render_perspective

df = pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]})


@render_perspective
def grid():
    return df

The render function may return a pandas or polars DataFrame or a perspective.Table (both are coerced to a perspective.widget.PerspectiveWidget; requires pip install 'shinywidgets[perspective]'), or a perspective.widget.PerspectiveWidget widget. A pandas index becomes an index column.

Note

Accessing the rendered widget: the render function gains two read-only attributes: .value, the value it most recently returned, and .widget, the rendered PerspectiveWidget instance. Widget state can also be read reactively with .read("some_trait") (a shortcut for reactive_read() on .widget).

Responding to user interaction: read the viewer’s accessors in a reactive function (a @render.* function, @reactive.calc, or @reactive.effect), which re-runs whenever the user changes it, much like input.my_slider():

  • grid.filter(): the viewer’s current filters.
  • grid.view_config(): the viewer’s structural configuration (plugin, columns, group/split by, aggregates, sort, filter, expressions).

They can’t be read inside this output’s own render function, which builds a new viewer each time it runs. For anything beyond them (e.g., theme or title), use grid.read() – but unlike the accessors (which are seeded at render), a cosmetic trait read this way can be None until the first browser sync, so guard with req().

Mutating the widget: use .widget to update the rendered viewer in place (e.g., .widget.update() streams new rows); trait changes are synced to the browser. Perspective keeps every table it hosts in memory for the life of the process, so a table created from a DataFrame is not freed when the output re-renders. To change the data, prefer updating the existing viewer in place: .widget.update(df) appends rows (or upserts them, for a table with an index), and .widget.clear() followed by .widget.update(df) swaps them out. (.widget.replace() also swaps rows, but doesn’t accept a DataFrame.)

A viewer’s browser can list, read, and write every table on its widget’s Perspective server. A DataFrame gets a server of its own, so other sessions can’t reach its data. Tables made with perspective.table() (and widgets made with PerspectiveWidget(df)) live on Perspective’s process-wide server, which every session’s viewer can reach, so only use them for data that every user may see and edit. For per-user data, create the table on a private server instead: perspective.Server().new_local_client().table(df).

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.

Each :
which :

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

filter() The viewer’s current filters, as [[column, op, value], ...] (e.g. [["x", ">", 1]]); [] while nothing is filtered.
view_config() The viewer’s current configuration as a dict with the keys plugin, columns, group_by, split_by, aggregates, sort, filter, and expressions – the subset of PerspectiveWidget.save() that determines the rows shown, so cosmetic changes (opening the settings panel, switching the theme) don’t re-fire it. plugin is viewer-only (it says how the rows are displayed, not which rows), so exclude it before reproducing the rows: widget.table.view(**{k: v for k, v in cfg.items() if k != "plugin"}). Row clicks and selections never reach Python. Not rate-limited.

filter()

render_perspective.filter()

The viewer’s current filters, as [[column, op, value], ...] (e.g. [["x", ">", 1]]); [] while nothing is filtered.

Edits made in the viewer’s settings panel sync back to Python. Not rate-limited (filter edits are discrete gestures, and the renderer’s rate_policy/rate_delay parameters don’t apply to it – they only govern accessors that declare a default, plus reactive_read() on the widget).

view_config()

render_perspective.view_config()

The viewer’s current configuration as a dict with the keys plugin, columns, group_by, split_by, aggregates, sort, filter, and expressions – the subset of PerspectiveWidget.save() that determines the rows shown, so cosmetic changes (opening the settings panel, switching the theme) don’t re-fire it. plugin is viewer-only (it says how the rows are displayed, not which rows), so exclude it before reproducing the rows: widget.table.view(**{k: v for k, v in cfg.items() if k != "plugin"}). Row clicks and selections never reach Python. Not rate-limited.