== Interactive Widgets == Currently, visualization in SageMath is mostly not interactive. However, there are approaches to build interactive widgets for the Jupyter notebook: * [[interact|@interact]] creates configurable widgets. * [[https://github.com/flatsurf/ipymuvue/tree/master/examples|ipyμvue]] can be used to write Jupyter widgets in Python that work with SageMath. * matplotlib plots can be made interactive. === Example: Interactive matplotlib Plots === {{{#!python # Enable interactive matplotlib output in Jupyter. %matplotlib nbagg # Better, sage -pip install ipympl and then # %matplotlib ipympl class DynamicPlot(sage.plot.primitive.GraphicPrimitive): r""" A dynamic 2D plot that redraws when it is dragged around. INPUT: - ``create_plot`` -- a callable that creates a plot for given ``xmin``, ``ymin``, ``xmax``, ``ymax`` bounds. - ``xmin``, ``ymin``, ``xmax``, ``ymax`` -- initial bounds of the 2D plot. """ def __init__(self, create_plot, xmin=-1, xmax=1, ymin=-1, ymax=1, options={}): self._create_plot = create_plot self._xmin = xmin self._xmax = xmax self._ymin = ymin self._ymax = ymax super().__init__(options) def _render_on_subplot(self, subplot): def redraw(_=None): try: # Clear the subplot before redrawing. Otherwise, we would pile up lots # of identical plots that take more and more time to draw. # Note that this will also clear other primitives from this subplot. subplot._children = [] xlim = subplot.axes.get_xlim() ylim = subplot.axes.get_ylim() import sage.misc.verbose verbose = sage.misc.verbose.get_verbose() # Silence warnings about undefined values in the plotted function. sage.misc.verbose.set_verbose(-1) try: # Plot all the objects produced by create_plot(). for object in self._create_plot(xmin=xlim[0], xmax=xlim[1], ymin=ylim[0], ymax=ylim[1])._objects: object._render_on_subplot(subplot) finally: sage.misc.verbose.set_verbose(verbose) except Exception: # Unfortunately, there is no easy way to display an error message in a matplotlib callback. # Errors are shown in the terminal where Jupyter was started. subplot.clear() raise # Redraw when the plot is dragged around. subplot.axes.callbacks.connect("ylim_changed", redraw) subplot.axes.callbacks.connect("xlim_changed", redraw) # Draw the plot in the initial bounds. redraw() def get_minmax_data(self): r""" Return the initial bounds of this plot to focus the camera here. """ return dict(xmin=self._xmin, ymin=self._ymin, xmax=self._xmax, ymax=self._ymax) def show(self): r""" Create a matplotlib figure and show this plot. """ g = Graphics() g.add_primitive(self) import matplotlib.pyplot as plt figure = plt.figure() g.matplotlib(figure=figure) }}} {{{#!python # We plot an infinite ray from the origin. def create_plot(*, xmin, ymin, xmax, ymax): def ray(x): if x > 0: return x + sin(x) return plot(ray, alpha=.5, xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax) DynamicPlot(create_plot).show() }}} {{{#!python # We plot a parabola def create_plot(*, xmin, ymin, xmax, ymax): return plot(x^2, alpha=.5, xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, # Adaptive recursion slows down the plotting when we zoom out a lot, so we disable # it for this simple function. adaptive_recursion=0) DynamicPlot(create_plot).show() }}}