bootstack.Chart#
- class bootstack.Chart(figure=None, *, render=None, signal=None, data_source=None, debounce=0, toolbar=False, themed=True, seaborn_desat=0.75, parent=None, **kwargs)#
Bases:
PublicWidgetBaseEmbed a matplotlib figure in a bootstack app, themed to match.
Chartis the bridge between bootstack and the scientific Python plotting stack. You build a plot with matplotlib (or seaborn, which draws onto the same axes) and hand the figure to aChart; it embeds the figure as a first-class widget that fills its space and recolors its chrome — figure and axes backgrounds, spines, ticks, and text — to match the active theme, flipping with light/dark like everything else in the app.Chartis deliberately not a plotting API: it does not wrap matplotlib’s drawing calls. You keep the full expressive power of matplotlib/seaborn and bootstack owns only the embedding, theming, and redraw.matplotlib is an optional dependency. Install it with the visualization extra:
pip install bootstack[viz](orbootstack[viz-seaborn]to add seaborn). Constructing aChartwithout matplotlib installed raises abootstack.errors.BootstackErrorexplaining how to install it.Build the figure with matplotlib’s object API (
matplotlib.figure.Figure) rather thanpyplot— an embedded figure must be a standalone object, andpyplotwould also open a separate window.There are two ways to use it:
Figure host — pass a
figureyou built. The chart embeds it and recolors its chrome to the theme. You own the figure; theming the data series is up to you.Managed render — pass a
rendercallback and a reactive source: one or moresignalobjects, or adata_source. The chart owns the figure and redraws for you: each redraw clears the axes, applies the theme as matplotlib settings — including a semantic accent color cycle so multiple series are on-brand — then calls yourrender. It re-renders when the theme changes or the bound signal / data source updates:count = bs.Signal(20) def render(ax, n): ax.plot(range(n), [i * i for i in range(n)]) bs.Chart(render=render, signal=count) # redraws when `count` changes
With
data_source=,renderreceives the source’s records (a list of dicts) and the chart re-renders whenever the source changes — so a chart and aDataTablecan share one source and stay in sync.
- Parameters:
figure (Any) – The matplotlib
Figureto display (figure-host mode). Omit to start empty, or when usingrender.render (Callable | None) – A drawing callback for managed mode, called as
render(ax)orrender(ax, data)wheredatais the bound signal value(s). The chart clears and re-themes the axes before each call, so just draw.signal (Any) – A
bootstack.Signal(or a list of them) whose value is passed torenderasdata; the chart re-renders when it changes. Requiresrender. Mutually exclusive withdata_source.data_source (Any) – A data source (any
bootstack.datasource) to plot. Its records are passed torenderasdata(a list of dicts), and the chart re-renders whenever the source changes — so a chart and aDataTablecan share one source and stay in sync. Reads all rows matching the source’s currentwhere/order, so filter or sort the source to scope or shape what is plotted. Requiresrender; mutually exclusive withsignal.debounce (int) – Milliseconds to coalesce rapid signal changes before re-rendering.
0(default) re-renders on every change.toolbar (bool | list[str]) – Show a themed navigation toolbar above the chart.
Trueincludes all standard tools —home,back,forward,pan,zoom,save; pass a list to choose a subset in order (for example["pan", "zoom", "save"]), or[]for an empty bar you fill yourself. Defaults toFalse(no toolbar). The toolbar drives matplotlib’s own navigation, so pan and zoom work on any embedded figure; save routes through bootstack’s file dialog. Add your own buttons through thetoolbarproperty.themed (bool) – Whether bootstack colors your data series.
True(default) applies the semantic accent color cycle (and seaborn palette) so multiple series are on-brand. PassFalseto keep your own series colors — the chart’s chrome (background, axes, text, ticks, grid) still tracks the theme so it fits the app, but the accent cycle and seaborn seeding are not imposed, leaving data colors to you (or to your own matplotlib style). Per-series colors you set explicitly always win regardless of this flag.seaborn_desat (float) – How much to soften the accent colors when seeding seaborn’s palette, in
[0, 1](seaborn’s owndesat). Defaults to0.75— seaborn plots are usually area-filled, where the full accent saturation reads heavy, so the seeded palette is muted to suit its aesthetic while staying on-brand.1.0keeps the accents fully saturated. Only affects seaborn (the matplotlib line cycle stays vivid) and only whenthemed=True.parent (Any) – Explicit parent widget. If omitted, the current context-stack container is used.
**kwargs (Any) – Layout placement options applied by the parent container (e.g.
grow,horizontal,row,column), plus container styling such assurfaceandpadding. See Arranging Widgets.
- property ax: Any#
The figure’s primary
Axes, creating one if the figure is empty.A convenience for the common single-plot case:
chart = bs.Chart() chart.ax.plot([1, 2, 3]) chart.draw()
- property figure: Any#
The embedded matplotlib
Figure.Assign a new
Figureto swap what is displayed; the old canvas is torn down and the new figure is themed and drawn.
- property is_attached: bool#
Whether the widget is currently placed in its layout.
Truewhile the widget occupies space in its parent;Falseafterdetach(or before it has ever been placed). A detached widget keeps its state and can be returned to the layout withattach.
- property schedule: Schedule#
Scheduler tied to this widget’s lifetime.
All jobs are automatically cancelled when the widget is destroyed. First access creates the
Scheduleinstance; subsequent accesses return the same instance.Usage:
self.schedule.delay(500, callback) self.schedule.every(1000, tick) job = self.schedule.idle(refresh) job.cancel()
- property toolbar: Any#
The navigation toolbar, for adding your own buttons.
Available only when the chart was created with
toolbar=True. Returns abootstack.Toolbarwhose built-in navigation buttons (home, back, forward, pan, zoom, save) sit on the left and the coordinate readout on the right; anything you add lands beside the built-in tools:chart = bs.Chart(render=render, signal=sig, toolbar=True) chart.toolbar.add_divider() chart.toolbar.add_button(icon="arrow-clockwise", on_click=refresh) chart.toolbar.add_widget(bs.ThemeToggle)
It is the full
Toolbarsurface —add_button,add_divider,add_widget, and the rest all work.- Raises:
BootstackError – if the chart was created without
toolbar=True.
- animate(setup, update, *, interval=30, frames=None)#
Drive a smooth, high-performance animation via blitting.
Unlike the managed
render=path (which rebuilds the whole figure on each update — right for occasional data changes, but limited to ~30 fps), animation updates artists in place and redraws only them over a cached background, sustaining high frame rates.- Parameters:
setup (Callable) – Called once with the themed
Axes. Create your artists with initial or empty data and set FIXED axis limits — blitting needs stable axes — then return the artist, or a list of artists, to animate.update (Callable) – Called every frame as
update(value, artist)(orupdate(value)), whereartistis whateversetupreturned. For a continuous animation (the default)valueis the elapsed time in seconds since the animation started — drive motion by this and the apparent speed stays constant even when frame timing jitters. If you passframes,valueis the current frame value instead. Mutate the artist’s data in place; do not create new artists or rescale the axes.interval (int) – Target milliseconds between frames. Default 30 (~33 fps).
frames (Any) – An iterable of frame values to step through fixed data, or None (default) for a continuous, time-driven animation.
- Returns:
A
ChartAnimationhandle — callstop()/start()to control it. The animation stops automatically when the widget is destroyed.- Return type:
ChartAnimation
- attach(**kwargs)#
Return a detached widget to its layout, optionally moving it.
With no arguments, restores the widget to exactly where
detachtook it from. Any layout kwargs accepted by the original placement (e.g.fill,expand,anchor,sticky,margin) override the stored options. For stacked widgets,index=sets the position among the currently attached siblings (or pass an explicitbefore=/after=sibling); without one, the snapshotted position is used.Calling
attachon a widget that is already attached moves it (the kwargs are re-applied). Fireson_attach.- Parameters:
**kwargs (Any) – Layout placement options to override for this placement.
- Raises:
ParentResolutionError – If the widget was never placed in a layout.
- destroy()#
Destroy the widget and release the resources it holds.
Removes the widget from its parent, destroys its children, and cancels any pending or repeating jobs on its
schedule. After this the widget must not be used again. Destroying a container destroys everything inside it.
- detach()#
Remove the widget from its layout without destroying it.
The widget stops occupying space but keeps its state, children, and event bindings, ready to be returned with
attach. The current position is snapshotted so a plainattach()restores it exactly — for stacked siblings this is the index among the currently attached siblings, so detaching other siblings first shifts that index.Calling
detachon a widget that is already detached, or one that was never placed in a layout, does nothing. Fireson_detach.
- draw()#
Redraw the figure after mutating it in place.
Call this after drawing onto
ax(or the figure) so the change appears. Theme changes redraw automatically — this is for your own updates.
- emit(event, *, data=None)#
Fire a named event on this widget, as if it produced the event itself.
This is how a composite widget surfaces high-level activity to its listeners, and the generic counterpart to the
on_*()shorthands for firing events that have no dedicated method.- Parameters:
event (str) – The event name, unprefixed — the same name you pass to
on()or anon_<event>()shorthand (e.g.'change','select').data (Any) – The payload delivered to handlers. For a data-carrying event, pass the matching payload dataclass from
bootstack.events— the same object anon_<event>()handler receives. Leave as None for native events (click, hover, focus, …), which carry no payload.
Example
widget.emit("change", data=bs.events.ChangeEvent(value=new_value))
- on(event, handler=None)#
Bind
handlertoevent, or return a composableStream.With a handler — binds immediately and returns a
Subscription:sub = widget.on("change", handler) sub.cancel()
Without a handler — returns a
Streamfor operator chaining. The Tk binding is created lazily when.listen()is called:sub = widget.on("change").debounce(300).listen(handler) sub.cancel()
- Parameters:
event (str) – Event name (e.g.
"change","click").handler (Callable[[Any], Any] | None) – Optional callback. If omitted, a
Streamis returned.
- Returns:
Subscriptionwhen a handler is provided;Streamotherwise.- Return type:
- on_attach(handler=None)#
Register a callback fired when the widget enters the layout.
Fires each time the widget becomes visible in its parent — on initial placement and on every
attach. Pair it withon_detachto keep per-visibility resources (timers, observers) tied to the widget’s presence on screen. The handler receives a curatedEvent.- Parameters:
handler (Callable[[Event], Any] | None) – Called when the widget is attached. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- on_destroy(handler=None)#
Register a callback fired when the widget is destroyed.
Fires once, as the widget is torn down — the place to release resources the widget owns that aren’t cleaned up automatically (file handles, observers, external subscriptions). The handler receives a curated
Event.- Parameters:
handler (Callable[[Event], Any] | None) – Called as the widget is destroyed. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- on_detach(handler=None)#
Register a callback fired when the widget leaves the layout.
Fires each time the widget stops occupying space in its parent — on
detachand when an ancestor hides it. Pair it withon_attachto release per-visibility resources. The handler receives a curatedEvent.- Parameters:
handler (Callable[[Event], Any] | None) – Called when the widget is detached. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type: