render_altair

render_altair class

Reactively render an altair chart.

Usage Source

render_altair(
    _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_altair is a decorator that creates a render function – a function that returns the chart to display and re-runs when the reactive values it reads change:

import altair as alt
from shiny.express import render
from vega_datasets import data

from shinywidgets import render_altair

cars = data.cars()


@render_altair
def chart():
    brush = alt.selection_interval(name="brush")
    return (
        alt.Chart(cars)
        .mark_point()
        .encode(x="Horsepower", y="Miles_per_Gallon")
        .add_params(brush)
    )


@render.data_frame
def selected_cars():
    # an empty frame until the user brushes on the chart
    return chart.selected_rows("brush")

The render function may return either an altair.Chart (coerced to an altair.JupyterChart widget) or an altair.JupyterChart widget (requires pip install 'shinywidgets[altair]').

Note

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

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

  • chart.selected("brush"): the selection’s current state.
  • chart.selected_rows("brush"): the rows of the chart’s data it selects, evaluated the way Vega-Lite does (including binned and date fields).

Always give selections a name (alt.selection_interval(name="brush")) and pass it to these methods; the name may be omitted only when the chart has a single selection.

Mutating the widget: trait changes on .widget are synced to the browser. Selections can’t be set from Python, but params can: setting chart.widget.params.<name> = value from a @reactive.effect updates the chart in place without re-sending its data. Prefer params (or a re-render) over replacing chart.widget.chart, which re-sends the data, drops the container sizing described below, and replaces chart.widget.selections and chart.widget.params with new objects – so observers registered on the old ones (altair’s documented jchart.selections.observe(...) idiom) silently stop firing. Register observers on chart.widget, and re-register them if you do replace chart.widget.chart.

Sizing: a chart without an explicit width or height is sized to fill its output ("container"). Concatenated, faceted, and repeated charts keep their natural size.

Offline use: JupyterChart loads its JavaScript from a CDN; call alt.JupyterChart.enable_offline() (requires vl-convert-python) for apps that run without internet access.

Behavior details:

  • Unlike other renderers’ accessors, selected() isn’t None once the chart has rendered: altair reports its selections from the start, so it’s an empty selection (and selected_rows() an empty frame) until the user selects.
  • A re-render (e.g. when the render function reads an input) gives the user a new chart, whose selections start out empty.
  • While the user drags, they update at most once the user pauses for 250 ms (see rate_policy and rate_delay).
  • They can’t be read inside this output’s own render function, which builds a new chart each time it runs. To change the chart in response, set a param (above) from a @reactive.effect.

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 selected() (debounced by default) and reactive_read() on the widget: "debounce" or "throttle". None (the default) keeps the debounce, and debounces reactive_read() only if rate_delay is given.

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

selected() The current state of one of the chart’s selections: an altair IntervalSelection, PointSelection, or IndexSelection (see altair.utils.selection), whose .value holds what’s selected.
selected_rows() The rows of df in a selection (see selected()), evaluated the way Vega-Lite does (including binned and temporal fields).

selected()

render_altair.selected(name: Optional[str] = None)

The current state of one of the chart’s selections: an altair IntervalSelection, PointSelection, or IndexSelection (see altair.utils.selection), whose .value holds what’s selected.

Unlike other renderers’ accessors, this is never None once the chart has rendered: until the user selects, it’s an empty selection. name is the name given to the selection (e.g. alt.selection_interval(name="brush")), and may be omitted when the chart has only one. Always name selections: altair’s generated names change between renders. Debounced (250 ms) by default; see the rate_policy and rate_delay parameters. Prefer declaring selections with fields= or encodings=: a bare alt.selection_point() selects positional row indices, which are only meaningful for non-aggregated charts and can silently point at different rows after a re-render with new data.

selected_rows()

render_altair.selected_rows(
    name: Optional[str] = None,
    *,
    df: Optional[Union[IntoDataFrameT, Dict[str, IntoDataFrameT]]] = None,
)

The rows of df in a selection (see selected()), evaluated the way Vega-Lite does (including binned and temporal fields).

df defaults to the chart’s own data frame (pandas or polars); pass it for charts whose data is a URL. When the selection touches several views with different data – a layered or concatenated chart whose subcharts use different data frames – each view’s data is filtered by its own selection entries and the return is a dict of frames keyed by view name (give subcharts a name= for readable keys); df may then be passed as a dict keyed by view name. When the selection touches one view, or views sharing the identical data frame object, a single frame is returned. Identical means the same object, not an equal one: layers built from separate copies of one frame (e.g. alt.Chart(df.copy()) per layer) don’t count as shared, so the return is a dict – give every subchart the same frame object, or pass the frames as a dict keyed by view name. Returns an empty frame (or a dict of empty frames) while nothing is selected. Dates are compared in UTC, reading time-zone-naive columns in the browser’s time zone, as Vega-Lite does. A bare point selection (positional row indices, from alt.selection_point() without fields=/encodings=) isn’t supported on a chart with several datasets – declare the selection with fields= or encodings= instead.