iambirdy
iambirdy

Reputation: 1

How to update a matplotlib graph/ display widget using Jupyter widgets

I'm using a widgets.Output to display a matplotlib figure. I would like to be able to update the data shown in the figure/ axis by clicking a button, without generating a new figure.

Ultimately, the button function will retrieve data from a selected file - the graph does not need to update automatically like an animation.

In a previous version, I used:

import ipywidgets as wg
import matplotlib.pyplot as plt

output = wg.Output()
 
def make_plot(b):
    with output:
        f, ax = plt.subplots(figsize=figsize)
        ## details of plot here

output.on_displayed(make_plot)

but now get the message that wg.Output() has no attribute on_displayed.

I've tried the below, but it does not change the axis after clicking the button (the "confirm triggering function") does print correctly.

class GUI(object):
    def __init__(self):
        import ipywidgets as wg
        import matplotlib.pyplot as plt
        import numpy as np
        
        self.display_button = wg.Button(description="Display graph")
        self.graph_display = wg.Output()

        ## Shows empty axis but does not update with function when included here:
        with self.graph_display: 
            self.fig, self.ax = plt.subplots()
        
        def on_display_button_clicked(b):
            print("confirm triggering function")
            with self.graph_display:
                ## Does generate graph on button click when line below is uncommented, *but* appends a new graph each time
                ## self.fig, self.ax = plt.subplots()                
                self.ax.scatter(np.random.rand(10), np.random.rand(10))

        self.display_button.on_click(on_display_button_clicked)
        self.vbox = wg.VBox([self.display_button,
                             self.graph_display])

gui = GUI()
gui.vbox

I also tried modifying the example at https://stackoverflow.com/a/51060721, which works for displaying the specified graph when run once.

## modified from https://stackoverflow.com/a/51060721,

import matplotlib.pyplot as plt
import pandas as pd
import ipywidgets as widgets
import numpy as np

out1 = widgets.Output()
data1 = pd.DataFrame(np.random.normal(size = 50))

tab = widgets.Tab(children = [out1])
tab.set_title(0, 'First')
display(tab)

with out1:
    fig1, axes1 = plt.subplots()
    axes1.imshow('image.png')
    data1.hist(ax = axes1)
    plt.show(fig1)

However if I then run e.g. axes1.set_title("Graph title") in another jupyter cell, this does not modify the axes1 display.

Upvotes: 0

Views: 32

Answers (2)

Wayne
Wayne

Reputation: 9984

This upper section was meant to have an answer/suggestion for first part of the post; however, OP found a solution that worked before I got back to that part. (The priority was to provide expanded information and code to go along with my comment and the below section is for that.) ...



I commented on the part that begins "I also tried modifying the example at stackoverflow.com/a/51060721,". I need SO's 'answer' space to provide a possibility with code. (So think of this section as an expansion on my comments about the bottom section of your post in relation to the referenced answer at '51060721'.) You can do the adding of the title in a second cell, but it won't work in the simple way you try because on a Jupyter notebook environment, calling plt.show() within an output widget like out1 actually closes the figure within that specific output cell. Any subsequent modifications to the figure (like setting the title) after calling plt.show() won't have any effect because the figure is no longer active.

The following would work:

import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as widgets

out1 = widgets.Output()
data = np.random.rand(10, 10)

tab = widgets.Tab(children=[out1])
tab.set_title(0, 'First')
display(tab)

with out1:
    fig1, axes1 = plt.subplots()
    axes1.imshow(data, cmap='viridis')
    plt.show(fig1)  # Display the initial plot 

Then in the next cell:

with out1:
    out1.clear_output(wait=True)  # Clear the previous output
    fig1, axes1 = plt.subplots()  # Create a new figure and axes
    axes1.imshow(data, cmap='viridis') 
    axes1.set_title("New Title") 
    plt.show(fig1) 

Of course, that goes against your hope of, "I would like to be able to update the data shown in the figure/ axis by clicking a button, without generating a new figure." However, my understanding an Output widget doesn't inherently provide a mechanism to directly access and modify the internal state of a figure object created within a previous cell.

Upvotes: 0

iambirdy
iambirdy

Reputation: 1

The below integrates the clear_output function from Wayne's answer above, and is triggered by a button click.

Although it does generate a new figure each time, the previous one is closed and replaced so only one figure is displayed at a time.

matplotlib nbagg enables the figure to be plotted within the VBox and doesn't require the plt.show(fig) line (using matplotlib inline plots the new figure below the VBox

%matplotlib nbagg

class Axis(object):
    def __init__(axis_self):
        from IPython.display import display
        import ipywidgets as wg
        import numpy as np
        import matplotlib.pyplot as plt

        axis_self.show_graph_button = wg.Button(description="Display graph")
        axis_self.output = wg.Output() ## For output to display within tab, need %matplotlib nbagg at beginning
        
        axis_self.B_manual_select_area = wg.Button(description="Manually select area")
        
        
        axis_self.vbox = wg.Tab([wg.VBox([axis_self.show_graph_button,
                         axis_self.output])
                         ])

        def on_show_graph_button_clicked(b):
            with axis_self.output:
                axis_self.output.clear_output(wait=True)
                fig, ax = plt.subplots()
                ax.plot(np.random.rand(10), np.random.rand(10))

        axis_self.show_graph_button.on_click(on_show_graph_button_clicked) 
        
axis_self = Axis()
axis_self.vbox

Upvotes: 0

Related Questions