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 df

The 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 underlying grid.grid.widget.selection trait directly with reactive_read(), parse it with ast.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 as grid.clicked() – the displayed columns plus mols2grid-id, the cell’s img markup, the SMILES column, and any tooltip-only columns – for click-to-inspect panels and drill-downs. The payload can be wide (img alone is the full SVG markup), so pick the keys you display rather than stringifying the whole dict – and clicked() raises without clicks=True or a Python callback. clicked() is None until 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’s mols2grid-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’s filter_mask trait 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’ grid outputs don’t collide). Since the generated name is stable across re-renders, selections survive them (the renderer passes cache_selection=True unless you say otherwise; with an explicit name it defers to mols2grid’s default, since an explicit name may be deliberately shared across grids). A MolGrid you construct keeps its own name – 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 a MolGrid is 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 explicit height on 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 a grid of tens of thousands of molecules makes each render a large server-to-browser message. No websocket limit rejects it (Shiny’s 16 MiB --ws-max-size limit applies only to browser-to-server messages), so expect a slow first paint, not an error. To bound the grid rather than its container, pass iframe_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_height bounds the iframe, not the output, so pair it with fill=False to actually shrink the output in a filling container. (iframe_width, iframe_allow, and iframe_sandbox are forwarded to the iframe the same way.)

Parameters

smiles_col : Optional[str] = 'SMILES'

The DataFrame column holding SMILES strings. Set to None when using mol_col instead.

mol_col : Optional[str] = None

The DataFrame column holding RDKit molecules (e.g., from mols2grid.sdf_to_dataframe(), which produces both). With smiles_col=None, a SMILES column named "SMILES" is generated from the molecules (mols2grid itself mis-renders a None SMILES 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 explicit callback is 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 (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; in practice both are inert for this renderer – no accessor declares a default policy, so rate_policy/rate_delay don’t affect selected() or clicked() (each fires at most once per gesture), and the policy registers on the rendered Box, not the inner MolGridWidget whose selection/callback_kwargs traits hold the state, so reads of those aren’t rate-limited either.

rate_delay : Optional[int] = None

The rate-limit window in seconds (default 0.25). 0 disables 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 to MolGrid() – so render_mols2grid(addStereoAnnotation=True) sets an RDKit draw option, the same passthrough mols2grid.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 and keeps its value across re-renders until the user clicks the new grid. The payload holds every displayed column plus mols2grid-id, and always also includes img (the cell’s full SVG markup) and the SMILES column, even when they aren’t displayed, as well as any tooltip-only columns. 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().