render_matplotlib
render_matplotlib class
Reactively render a matplotlib figure as an interactive ipympl widget.
Usage Source
render_matplotlib(
_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_matplotlib is a decorator that creates a render function – a function that returns the figure to display and re-runs when the reactive values it reads change:
import matplotlib.pyplot as plt
from shinywidgets import render_matplotlib
@render_matplotlib
def plot():
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
return figThe render function may return a matplotlib.figure.Figure, a matplotlib.axes.Axes (rendered via its figure), or an ipympl.backend_nbagg.Canvas widget. Figures and axes are coerced to a Canvas (requires pip install 'shinywidgets[matplotlib]'), so this also works with libraries built on matplotlib (e.g., seaborn or pandas.DataFrame.plot()). A coerced canvas’s header (the “Figure N” title) is hidden by default; a Canvas you construct yourself keeps its own settings (e.g., header_visible=True). In a filling layout, the figure is resized to fit its container. With an explicit height, the figure keeps its figsize unless the output is also fillable=True.
Unlike @render.plot, which draws a static image, the figure stays alive on the server for as long as it is displayed, so it can respond to the user. Like @render.plot, a figure created through pyplot is closed (as by plt.close()) once it’s rendered. Still, prefer creating figures explicitly (e.g., Figure(layout="constrained") or plt.subplots()) over pyplot’s implicit current figure or axes (plt.plot(), plt.gca(), or seaborn functions without ax=), which are shared by every output and session in the process.
Create figures with a non-interactive backend: matplotlib.figure.Figure() directly, or pyplot under the agg backend (the default when no display is available, as on a server; select it locally with matplotlib.use("agg") before creating figures). A figure created under a GUI backend (Qt, Tk, macOS, …) can still be rendered – its window is destroyed and ipympl’s canvas replaces its own – but that path depends on platform-specific behavior, so it is supported on a best-effort basis and warns at render time.
Note
Accessing the rendered widget: the render function gains two read-only attributes: .value, the value it most recently returned, and .widget, the rendered Canvas instance. Widget state can also be read reactively with reactive_read().
Responding to user interaction: the output has methods that read what the user does in the plot, much like input.my_slider() reads an input. Call them in a reactive function (a @render.* function, @reactive.calc, or @reactive.effect), which then re-runs whenever the interaction changes:
clicked(): the most recent mouse press on an axes.picked(): the most recent pick of an artist created withpicker=.limits(ax=0): an axes’ x/y limits after a zoom or pan.
How they behave:
clicked()andpicked()returnNoneuntil the interaction happens. That’s a normal state, not an error, so check for it, or usereq(plot.clicked())to wait for it; they keep their value across re-renders (the payload then refers to the previous render’s figure).limits()waits for the first render, then always holds the axes’ current limits.- Each press fires
clicked()– a double-click arrives as three presses. limits()updates at most once the user pauses for 250 ms (seerate_policyandrate_delay).- They can’t be read inside this output’s own render function, which builds a new figure each time it runs. To change the figure in response, modify
.widgetfrom a@reactive.effectinstead.
For anything else, matplotlib’s own callbacks (e.g., fig.canvas.mpl_connect("motion_notify_event", f)) still work too, and may set reactive.values.
Mutating the widget: to update the rendered figure without losing the user’s zoom, modify the artists of .widget.figure and call .widget.draw_idle().
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 (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
limits()(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.
Examples
from matplotlib.figure import Figure
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_matplotlib
app_ui = ui.page_fluid(
ui.input_slider("n", "Points", 10, 100, 50),
output_widget("plot"),
ui.output_text("visible"),
)
def server(input, output, session):
xlim = reactive.value((0.0, 0.0))
@render_matplotlib
def plot():
fig = Figure(layout="constrained")
ax = fig.subplots()
ax.plot(range(input.n()))
ax.callbacks.connect("xlim_changed", lambda ax: xlim.set(ax.get_xlim()))
return fig
@render.text
def visible():
lo, hi = xlim()
return f"Showing x from {lo:.1f} to {hi:.1f}"
app = App(app_ui, server)Each accessor can also be read by name – out.read("clicked") – 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 most recent mouse press on an axes, as a matplotlib.backend_bases.MouseEvent (.xdata/.ydata in the data coordinates of the axes under the press, .inaxes that axes). |
limits() |
The x and y limits of axes ax (an index into .widget.figure.axes), as ((x0, x1), (y0, y1)). |
picked() |
The most recent pick of an artist, as a matplotlib.backend_bases.PickEvent (.artist the picked artist, .ind the picked point indices). Only artists created with picker= (e.g., ax.plot(x, y, picker=5)) are pickable. |
clicked()
render_matplotlib.clicked()The most recent mouse press on an axes, as a matplotlib.backend_bases.MouseEvent (.xdata/.ydata in the data coordinates of the axes under the press, .inaxes that axes).
Returns None until the user clicks, and keeps its value across re-renders (the payload then refers to the previous render’s figure), so use req(plot.clicked()) to wait for a click. Presses that start a toolbar pan/zoom drag, or land outside any axes, don’t count. Every press fires – a double-click arrives as three presses. Not rate-limited.
limits()
render_matplotlib.limits(ax: int = 0)The x and y limits of axes ax (an index into .widget.figure.axes), as ((x0, x1), (y0, y1)).
Seeded from the figure’s current limits at render and updated after every zoom or pan (including ones made in Python). Reading it before the first render cancels silently (like req(False)); it never returns None after that. Debounced (250 ms) by default; see the rate_policy and rate_delay parameters. Raises IndexError when the figure has no axes ax.
picked()
render_matplotlib.picked()The most recent pick of an artist, as a matplotlib.backend_bases.PickEvent (.artist the picked artist, .ind the picked point indices). Only artists created with picker= (e.g., ax.plot(x, y, picker=5)) are pickable.
Returns None until the user picks, and keeps its value across re-renders (the payload then refers to the previous render’s figure). Every pick fires. Not rate-limited.