Updating matplotlib figure within Output-widget

Hi,

Thank you for welcoming me to the community, I have a small challenge I hope you can help me with.

I have a stand-alone python class which in essence

  • holds some parameters,
  • calculates some data series,
  • and creates a matplotlib plot of the data.

The python class works great and I use it in different small python scripts and jupyter notebooks.

I now want to make a simplistic ‘frontend’ to the class, using widgets and voila, to make the class and it’s functionality available to my colleagues, who are not acquainted with python, as a web-app.

I have made a small example demonstrating the functionality and my thoughts (here copied from a notebook).

Imports

%matplotlib inline

import matplotlib.pyplot as plt
import ipywidgets as ipw

import numpy as np

Class definition

class plotter_class:
    
    def __init__(self, p1=5.5, num_point = 500):
        
        self.param_1 = p1

        self.theta = np.linspace(0,10*np.pi,num=num_point, endpoint=True)
        
        self.series_1 = np.empty((num_point, ))
        
        self.calculate_series()
        
    def calculate_series(self, num_point=None):
        
        if num_point:
            self.theta = np.linspace(0,10*np.pi,num=num_point, endpoint=True)
            
            self.series_1 = np.empty((num_point, ))
            
        self.series_1 = self.param_1 * np.cos(self.theta*self.param_1/3.0)
        
    def plot_series(self, ax=None, grid=True):
        # If an axes object reference is not passed, a new axes object is created
        if not ax:
            fig, ax = plt.subplots(figsize=(12,8))
            
        ax.plot(self.theta, self.series_1, linewidth=1.5, label='Series 1')
            
        if grid:
            ax.grid()


PC = plotter_class()

The intend with the ‘plot_series’ method is that the user can either let the method create a new fig and axes object (works great for using notebooks, or pre-define an axes object and pass that reference to the method). This functionality have worked well for my ‘normal’ use.

For the controls, I want to have BoundedFloatText fields for setting the parameters and some check boxes for settings (e.g. show grid). I have created the widgets by:

p1_box = ipw.BoundedFloatText(
                                value=PC.param_1,
                                min=0,
                                max=100,
                                description='Parameter 1:',
                                disabled=False
)


grid_check = ipw.Checkbox(
                        value=True,
                        description='Show grid',
                        disabled=False)


comb_box = ipw.HBox([p1_box, grid_check])

out = ipw.Output()

with out:
    fig, axes = plt.subplots(figsize=(8,5))
    plt.show(fig)

display(comb_box)

display(out1)

Finally, I have created separate functions to update the PC-object and update the plot, and added an observer to the BoundedFloatText object as follows:

def updated_p1(change):
    PC.param_1 = change.new
    PC.calculate_series()
    update_plot()

def update_plot():
    
    # See also this thread https://github.com/jupyter-widgets/ipywidgets/issues/1940
    axes.clear()
    PC.plot_series(ax=axes)
    with out:
        clear_output(wait=True)
        display(axes.figure)
    
p1_box.observe(updated_p1, names='value')

So, the issue is that the ‘axes’ object within the output box does not refresh/update when the ‘update_plot’ function runs. I can in a new cell in the notebook type ‘fig’ (variable name of the figure obbject in the Output widget) and the updated figure appears. This leads me to believe that the axes does get updated in the kernel(?) but the update does not propagate into the widget.

Can you please give me some tips to fix my issue?

You seem to be doing display(out1) but then the actual plot is sent to a widget called out, maybe that’s the reason of the plot not updating? =)

The clearing of output also looks a bit strange to me, it should be out.clear_output(wait=True). Otherwise I get a NameError: name 'clear_output' is not defined with your code.

1 Like