render_mosaic_widget
render_mosaic_widget class
Reactively render a Mosaic dashboard of linked, DuckDB-backed views.
Usage Source
render_mosaic_widget(
_fn: Optional[ValueFn[MosaicWidget]] = 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,
allow_unrestricted_connection: bool = False,
)@render_mosaic_widget is a decorator that creates a render function – a function that returns the dashboard to display and re-runs when the reactive values it reads change:
import pandas as pd
from mosaic_widget import MosaicWidget
from shinywidgets import render_mosaic_widget
penguins = pd.DataFrame(
{
"species": ["Adelie", "Gentoo", "Adelie", "Gentoo"],
"body_mass": [3700, 5000, 3900, 5100],
}
)
spec = {
"plot": [
{
"mark": "dot",
"data": {"from": "penguins"},
"x": "species",
"y": "body_mass",
}
],
"width": 600,
"height": 300,
}
@render_mosaic_widget
def dashboard():
return MosaicWidget(spec, data={"penguins": penguins})The render function should return a mosaic_widget.MosaicWidget (requires pip install 'shinywidgets[mosaic_widget]'), built from a Mosaic spec and the data (e.g., a DataFrame) to register with DuckDB. Mosaic specs set the size of each plot, so the plots keep those pixel sizes while the output itself fills its container (or takes the explicit height). With an explicit height smaller than the plots, they overflow the output visibly (they aren’t clipped).
Note
Accessing the rendered widget: the render function gains two read-only attributes: .value and .widget, which are both the rendered MosaicWidget 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 dashboard’s accessors in a reactive function (a @render.* function, @reactive.calc, or @reactive.effect), which re-runs whenever the user brushes, much like input.my_slider():
dashboard.params(): the current selection params.dashboard.data(): the rows matching them, as a lazy DuckDB relation.
They can’t be read inside this output’s own render function, which builds a new dashboard each time it runs. For anything beyond them, use dashboard.read() (a shortcut for reactive_read() on dashboard.widget).
Mutating the widget: use .widget to update the rendered dashboard in place; trait changes on the widget are synced to the browser. Each re-render creates a new MosaicWidget and DuckDB connection and loads another copy of the widget’s JavaScript into the page, so to restyle a dashboard, prefer assigning a new spec to .widget.spec in place (this resets the current selections).
Mosaic runs its queries server-side, in the DuckDB connection the widget holds, so which connection that is decides what each Shiny session can see. DataFrames passed as data= are registered in a fresh in-memory connection per widget, which keeps sessions isolated. A shared con=, or a duckdb relation in data= (which mosaic-widget registers on DuckDB’s process-wide duckdb.default_connection()), is shared by every session: one session’s registered table replaces another’s under the same name, and Mosaic’s pre-aggregated mosaic.preagg_* tables are reused across sessions whose queries match. Only share a connection for data that is the same for every user.
Security: like render_embedding_atlas, Mosaic runs its queries by sending SQL from the browser to the widget’s connection, so without restrictions any visitor can send arbitrary SQL, including SQL that reads or writes files on the server. The renderer locks down the connection before display (SET enable_external_access = false; SET lock_configuration = true); pass allow_unrestricted_connection=True to opt out (e.g., a local, single-user app). A connection whose configuration is already locked is left untouched (its owner took control of its settings). This lockdown can reach a connection the app is still using elsewhere: DuckDB’s lockdown is permanent (it can’t be undone) and database-wide (it also locks every other cursor on the same database, not just the one it was set on). So for a shared connection as described above (con=, or a relation in data= landing on duckdb.default_connection()), the first render locks it for every other use in the process. Do any server-side file reads (read_parquet(), etc.) on that connection before the first render, or pass allow_unrestricted_connection=True for it.
Even with the lockdown, a visitor can still send expensive queries, which block the Python process while they run.
Parameters
allow_unrestricted_connection : bool = False-
When
True, the widget’s DuckDB connection is left as the render function returned it, without the lockdown described above. The default (False) is strongly recommended for deployed apps: an unrestricted connection lets any visitor run arbitrary SQL with the server process’s permissions. 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
params()(debounced by default) andreactive_read()on the widget:"debounce"or"throttle".None(the default) keeps the debounce, and debouncesreactive_read()only ifrate_delayis given. rate_delay : Optional[float] = None-
The rate-limit window in seconds (default 0.25).
0disables 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
data() |
The rows of a source table matching the current selections, as a lazy DuckDB relation (materialize it with .df(), .pl(), or .aggregate(...)). |
params() |
The dashboard’s current selection params: {name: {"value": ..., "predicate": ...}} for each declared selection, {} until the dashboard’s spec loads, and null-valued entries ({"value": None, ...}) for selections the user hasn’t brushed yet. |
data()
render_mosaic_widget.data(
table: Optional[str] = None,
*,
filter_by: Optional[Union[str, List[str]]] = None,
)The rows of a source table matching the current selections, as a lazy DuckDB relation (materialize it with .df(), .pl(), or .aggregate(...)).
table names the source table to query and may be omitted when the spec and the registered data name exactly one. filter_by names the selection (or selections) to filter by, with or without the leading "$" – the default, None, filters by every active selection. Re-reads whenever params() changes and runs the query on each read, so prefer calling it once inside a @reactive.calc.
Note that this queries the widget’s live params trait: while a brush is settling, the live value can be up to rate_delay (250 ms by default) ahead of the debounced value params() returns, and the two reconverge when the debounce settles. (MosaicWidget builds its filter SQL internally from the live trait, so the published value can’t be substituted.)
params()
render_mosaic_widget.params()The dashboard’s current selection params: {name: {"value": ..., "predicate": ...}} for each declared selection, {} until the dashboard’s spec loads, and null-valued entries ({"value": None, ...}) for selections the user hasn’t brushed yet.
predicate is the selection’s clauses ANDed together (ready to embed in SQL); value is the latest value only. Updates are debounced (250 ms) by default while the user drags a brush – see the rate_policy and rate_delay parameters.