render_mols2grid
render_mols2grid class
Reactively render a mols2grid molecule grid.
Usage Source
render_mols2grid(
_fn: Optional[ValueFn[Mols2GridValueT]] = None,
*,
smiles_col: Optional[str] = 'SMILES',
mol_col: Optional[str] = None,
clicks: bool = False,
width: Optional[str] = None,
height: Optional[str] = None,
fill: Optional[bool] = None,
fillable: Optional[bool] = None,
rate_policy: Optional[RatePolicy] = None,
rate_delay: Optional[int] = None,
**kwargs: Any,
)@render_mols2grid is a decorator that creates a render function – a function that returns the molecule grid to display and re-runs when the reactive values it reads change:
import pandas as pd
from shinywidgets import render_mols2grid
df = pd.DataFrame({"SMILES": ["CCO", "c1ccccc1"], "name": ["ethanol", "benzene"]})
@render_mols2grid
def grid():
return dfThe render function may return either a mols2grid.MolGrid, or a pandas.DataFrame with a SMILES column (smiles_col, "SMILES" by default) or an RDKit molecule column (mol_col), which is coerced to a MolGrid (requires pip install 'shinywidgets[mols2grid]'). Don’t return the object returned by mols2grid.display() – it’s an IPython HTML object, not a grid.
Extra keyword arguments are forwarded to mols2grid, split the way mols2grid.display() splits them: the rendering options (subset, tooltip, sort_by, n_items_per_page, selection, custom_css, transform, callback, …) go to the grid’s rendering, and everything else goes to MolGrid() – its constructor options (size, useSVG, prerender, cache_selection, removeHs, use_coords, coordGen, MolDrawOptions, name, rename), or, for any other name, an RDKit draw option (addStereoAnnotation=True, addAtomIndices=True, …). The grid is always displayed in an iframe (use_iframe=True), which keeps its jQuery/Bootstrap globals out of your app’s page.
Note
Accessing the rendered widget: the render function gains three read-only attributes: .value, the value it most recently returned, .widget, the rendered ipywidgets.Box holding the output, and .grid, the MolGrid instance backing it. The MolGridWidget that syncs selection state is .grid.widget.
Responding to user interaction: grid.selected() gives the current molecule selection, and grid.clicked() the most recently clicked molecule’s row data (with clicks=True):
- Selection (browser -> Python):
grid.selected()gives the selected rows reactively, as a dict, e.g.{0: "CCO"}. Row indices are positional against the rows as rendered.grid.grid.get_selection()returns the selected rows as a DataFrame. (If you read the underlyinggrid.grid.widget.selectiontrait directly withreactive_read(), parse it withast.literal_eval– it’s a repr string, not JSON.) - Clicks (browser -> Python): with
clicks=True, clicking a molecule’s image (a distinct gesture from the selection checkbox) makes its row’s data available reactively asgrid.clicked()– every displayed column plusmols2grid-id– for click-to-inspect panels and drill-downs. The payload can be wide (it includes the cell’s image markup), so pick the keys you display rather than stringifying the whole dict – andclicked()raises withoutclicks=Trueor a Pythoncallback.clicked()isNoneuntil the first click and keeps its value across re-renders (the last click still happened, though its row data refers to the previous render’s grid). Mutating the widget:grid.grid.filter(mask)(a boolean mask, Series, or list) hides non-matching molecules in the already rendered grid, without re-rendering. The mask is positional against the rows as rendered (mols2grid matches it up by each row’smols2grid-id), so pass it in the DataFrame’s row order – a Series taken from a filtered or reordered frame hides the wrong molecules. Call it in response to user input: a filter issued while the grid is still loading is recorded on the widget’sfilter_masktrait but not applied by mols2grid’s frontend (a mols2grid limitation), so a grid can’t start out filtered: re-issue it once the grid is visible, and filter the DataFrame in the render function for a grid that is filtered from the start. - Grid name: mols2grid keys selections in a process-global registry, so grids coerced from a DataFrame get a name unique to the session and the output (a stable
"shiny-<session>-<id>"slug, where the id is the output’s namespaced id, so two module instances’gridoutputs don’t collide). Since the generated name is stable across re-renders, selections survive them (the renderer passescache_selection=Trueunless you say otherwise; with an explicitnameit defers to mols2grid’s default, since an explicit name may be deliberately shared across grids). AMolGridyou construct keeps its ownname– give it a unique one (a grid still named"default"is rejected with an error, since it collides across sessions and across multiple grids on a page), and note that aMolGridis itself process-global state, so construct it inside the render function, not at the top level of the app. - Network access: the grid’s JavaScript (list.js, jQuery, RDKit MinimalLib, Bootstrap) loads from CDNs at render time, so the grid needs an internet connection (it renders empty, without an error, when the CDNs are unreachable). mols2grid hardcodes those URLs inside the iframe’s document, so they can’t be served from the app’s own assets; if you need an offline or air-gapped deployment, please open an issue.
- Sizing: the grid sizes its own height to fit its contents (mols2grid’s
fitIframe), so it scrolls with the page rather than stretching to a filling container; an explicitheighton the output acts as a viewport that clips the grid. Grid data is inlined into the iframe document (roughly 0.5-1 KB per molecule, depending on how many columns are shown), so grids in the tens of thousands of molecules can approach Shiny’s default 16 MiB websocket message limit (raise it withshiny run --ws-max-size). To bound the grid rather than its container, passiframe_height(e.g.render_mols2grid(iframe_height="400px")): it sets the iframe element’s height, which switches off mols2grid’s auto-fit so the grid scrolls inside the iframe.iframe_heightbounds the iframe, not the output, so pair it withfill=Falseto actually shrink the output in a filling container. (iframe_width,iframe_allow, andiframe_sandboxare forwarded to the iframe the same way.)
Parameters
smiles_col : Optional[str] = 'SMILES'-
The DataFrame column holding SMILES strings. Set to
Nonewhen usingmol_colinstead. mol_col : Optional[str] = None-
The DataFrame column holding RDKit molecules (e.g., from
mols2grid.sdf_to_dataframe(), which produces both). Withsmiles_col=None, a SMILES column named"SMILES"is generated from the molecules (mols2grid itself mis-renders aNoneSMILES column). clicks : bool = False-
Whether clicking a molecule’s image posts its row data for
.clicked()to read. Adds a click target to each cell. Ignored when an explicitcallbackis passed: a Python callback enables the same channel on its own, while a JavaScript callback runs in the browser and posts nothing – so in that combination.clicked()is unavailable (it raises, pointing here). 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, sorate_policy/rate_delaydon’t affectselected()orclicked()(each fires at most once per gesture). rate_delay : Optional[int] = None-
The rate-limit window in seconds (default 0.25).
0disables rate limiting. kwargs : Any = {}-
Extra keyword arguments forwarded to mols2grid (split between
MolGrid()and its rendering, as described above). The split is by name and by mols2grid’s own signatures: a name that the grid’s rendering accepts (subset,tooltip,sort_by, …) goes to the rendering, and any other name goes toMolGrid()– sorender_mols2grid(addStereoAnnotation=True)sets an RDKit draw option, the same passthroughmols2grid.display()provides.
Examples
import mols2grid
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_mols2grid
mols = mols2grid.sdf_to_dataframe(mols2grid.datafiles.SOLUBILITY_SDF)
app_ui = ui.page_fluid(
output_widget("grid"),
ui.output_text("n_selected"),
)
def server(input, output, session):
@render_mols2grid(subset=["ID"], tooltip=["ID", "SOL"])
def grid():
return mols
@render.text
def n_selected():
return f"{len(grid.selected() or {})} molecule(s) selected"
app = App(app_ui, server)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
clicked() |
The row data of the molecule image most recently clicked, or None if no click has happened yet (requires clicks=True or a Python callback). |
selected() |
The grid’s current molecule selection, as {row_index: SMILES}. |
clicked()
render_mols2grid.clicked()The row data of the molecule image most recently clicked, or None if no click has happened yet (requires clicks=True or a Python callback).
Unlike .selected() (the checkbox set), a click is retained last-click state: it carries the clicked row’s data (every displayed column, plus mols2grid-id) and keeps its value across re-renders until the user clicks the new grid. All values are strings except mols2grid-id (an int), and clicking the same molecule twice in a row does not re-fire (a mols2grid limitation: the trait only changes when the payload does). Raises without clicks=True or a Python callback.
selected()
render_mols2grid.selected()The grid’s current molecule selection, as {row_index: SMILES}.
Call it in a reactive context (a render function, @reactive.Calc, or @reactive.Effect) to be invalidated when the selection changes in the browser; outside a reactive context, it reads the current value without taking a dependency. Row indices are positional against the rows as rendered – after the data changes, the same index can refer to a different molecule. For the selected rows as a DataFrame, use .grid.get_selection().