matplotlibmatplotlib-widget

Matplotlib sliders on multiple figures


I am writing a Python tool that needs several figures open at the same time, each one with its own widgets (sliders, for the most part). I don't need any interactions across the figures here. Each figure is independent of the other ones, with its own plot and its own sliders affecting only itself.

I can get Matplotlib sliders working fine on a single figure, but I can't get them to work on multiple figures concurrently. Only the sliders of the LAST figure to open are working. The other ones are unresponsive.

I recreated my problem with the simple code below, starting from the example in the Matplotlib.Slider doc. If I run it as-is, only the sliders for the second figure (amplitude) works. The other doesn't. If I invert the two function calls at the bottom, it's the other way around.

I've had no luck googling solutions or pointers. Any help would be much appreciated.

I'm on Python 3.9.12, btw. I can upload a requirements file if someone tries and cannot reproduce the issue. Thank you!

import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.widgets import Slider
    
    
    # The parametrized function to be plotted
    def f(time, amplitude, frequency):
        return amplitude * np.sin(2 * np.pi * frequency * time)
    
    
    # Define initial parameters
    init_amplitude = 5
    init_frequency = 3
    t = np.linspace(0, 1, 1000)
    
    
    def create_first_fig():
    
        # Create the figure and the line that we will manipulate
        fig1, ax1 = plt.subplots()
        line1, = ax1.plot(t, f(t, init_amplitude, init_frequency), lw=2, color='b')
        ax1.title.set_text('First plot - interactive frequency')
        ax1.set_xlabel('Time [s]')

        # adjust the main plot to make room for the sliders
        fig1.subplots_adjust(left=0.25, bottom=0.25)

        # Make a horizontal slider to control the frequency.
        axfreq = fig1.add_axes([0.25, 0.1, 0.65, 0.03])
        freq_slider = Slider(
            ax=axfreq,
            label='Frequency [Hz]',
            valmin=0,
            valmax=30,
            valinit=init_frequency,
        )
    
        # register the update function with each slider
        freq_slider.on_changed(lambda val: update_first_fig(val, fig1, line1))
    
        plt.draw()
        plt.pause(0.1)
    
        return fig1
    
    
    # The function to be called anytime a slider's value changes
    def update_first_fig(val, fig, line):
        line.set_ydata(f(t, init_amplitude, val))
        fig.canvas.draw_idle()
        plt.pause(0.1)
    
    
    def create_second_fig():
    
        # Create the figure and the line that we will manipulate
        fig2, ax2 = plt.subplots()
        line2, = ax2.plot(t, f(t, init_amplitude, init_frequency), lw=2, color='r')
        ax2.title.set_text('Second plot - interactive amplitude')
        ax2.set_xlabel('Time [s]')
    
        # adjust the main plot to make room for the sliders
        fig2.subplots_adjust(left=0.25, bottom=0.25)
    
        # Make a vertically oriented slider to control the amplitude
        axamp = fig2.add_axes([0.1, 0.25, 0.0225, 0.63])
        amp_slider = Slider(
            ax=axamp,
            label="Amplitude",
            valmin=0,
            valmax=10,
            valinit=init_amplitude,
            orientation="vertical",
        )
    
        # register the update function with each slider
        amp_slider.on_changed(lambda val: update_second_fig(val, fig2, line2))
    
        plt.draw()
        plt.pause(0.1)
    
        return fig2
    
    
    # The function to be called anytime a slider's value changes
    def update_second_fig(val, fig, line):
        line.set_ydata(f(t, val, init_frequency))
        fig.canvas.draw_idle()
        plt.pause(0.1)

    
    figure1 = create_first_fig()
    figure2 = create_second_fig()
    
    plt.show()



I would expect the slider in both figures to work the way it does when I only open the corresponding figure. So far it's only the slider in the figure that's created last that works.

Edit in case someone else looks at this: see Yulia V's answer below. It works perfectly, including in my initial application. The site doesn't let me upvote it because I am too new on here, but it's a perfect solution to my problem. Thanks Yulia V!


Solution

  • You need to save the references to sliders as variables to make it work. No idea why, but this is how matplotlib works.

    Specifically, in your functions, you need to have

    return freq_slider, fig1
    ... 
    return amp_slider, fig2
    

    instead of

    return fig1
    ... 
    return fig2
    

    and in the main script,

    freq_slider, figure1 = create_first_fig()
    amp_slider, figure2 = create_second_fig()
    

    instead of

    figure1 = create_first_fig()
    figure2 = create_second_fig()