0

I want to control the execution of some functions, through using some buttons in matplolib. I want to pause the execution of a function, which is in my case to break the for loop, for that I used the following in my script:

def pause(mouse_event):
    global pauseVal
    pauseVal ^= True

def play(mouse_event):    
    for x in range(int(dataSlider.val),len(listOfMomentsVis)):
        if pauseVal == True:
            break
        else:
            image.set_data(listOfMomentsVis[x]) 
            fig.canvas.draw()
            dataSlider.set_val(x+1)
            time.sleep(timeSleep)
            print(x)

What's happening is the that the function would not execute the next time I try to execute it, not during the execution. In other words, the pause button won't be effective until the next execution.

My question is: how to break the for loop in the middle of execution through an outside button event? Is that doable or should be some other way? (Could make the for loop listen to the outside function?!)

PS: I found the following question with the same title, How to break a loop from an outside function, however, the answers were about breaking according to a value in the loop, not a value from outside.

updae

Here is my update version of the script. I took the comments and answers into consideration, made a class, still not getting the desired results.

class animationButtons():
    def __init__(self):
        self.pauseVal = False

    def backward(self, mouse_event):
        ref = int(dataSlider.val)
        if ref-1<0:
            print('error')
        else:
            image.set_data(listOfMomentsVis[ref-1])
            dataSlider.set_val(ref-1)

    def forward(self, mouse_event):
        ref = int(dataSlider.val)
        if ref+1>len(listOfMomentsVis):
            ref = int(dataSlider.val)-1
            print('error')
        else:
            image.set_data(listOfMomentsVis[ref])
            dataSlider.set_val(ref+1)
            print('from forward', dataSlider.val)

    def play(self, mouse_event):
        for x in range(int(dataSlider.val),len(listOfMomentsVis)):
            ref = int(dataSlider.val)
            dataSlider.set_val(ref+1)
            time.sleep(timeSleep)
            print('from play',ref)
            if self.pauseVal:
                break

    def playReverse(self, mouse_event):
        for x in range(int(dataSlider.val),0,-1):
            ref = int(dataSlider.val)
            dataSlider.set_val(ref-1)
            time.sleep(timeSleep)
            print('from play',ref)
            if self.pauseVal:
                break

    def pause(self, mouse_event):
        self.pauseVal ^= True
        print(self.pauseVal)

    def animate(self, val):
        ref = int(dataSlider.val)
        image.set_data(listOfMomentsVis[ref])
        print('from animate',ref)
        fig.canvas.draw()
philippos
  • 1,142
  • 5
  • 20
  • 41
  • 3
    Sounds like your problem really lies in the fact that you're executing everything in a single thread. You'd generally need a separate thread that handles mouse clicks (and other UI actions). In that case, your solution should work (ish). – acdr Aug 02 '17 at 15:19

2 Answers2

1

Callbacks from a GUI event handler should complete quick, otherwise they hold up the thread responsible for processing events. This means you cannot do things like: time.sleep(timeSleep) in the callback function.

Instead you can use a timer to periodically trigger an animation event and then get your play and pause buttons to stop and start the timer. The following example creates a moving sine wave that stops after it has moved one full wavelength. Pressing play triggers the animation if one is not already in progress, or resumes if there is a currently paused animation. Pause pauses the animation if one is currently playing.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button

fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2) # add space for buttons

x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))

class Example:

    steps = 200

    def __init__(self):
        self.step_index = 0
        self.timer = None

    def animate(self):
        print(self.step_index)
        # update the data
        line.set_ydata(np.sin(x + (2 * np.pi * self.step_index / self.steps)))
        self.step_index += 1
        plt.draw()        

        # stop if end of animation
        if self.step_index >= self.steps:
            self.timer = None
            return False

    def play(self, event):
        if not self.timer:
            # no current animation, start a new one.
            self.timer = fig.canvas.new_timer(interval=10)
            self.timer.add_callback(self.animate)
            self.step_index = 0
        self.timer.start()

    def pause(self, event):
        if not self.timer:
            return
        self.timer.stop()

ex = Example()
ax_pause = plt.axes([0.7, 0.05, 0.1, 0.075])
ax_play = plt.axes([0.81, 0.05, 0.1, 0.075])
pause_button = Button(ax_pause, 'Pause')
pause_button.on_clicked(ex.pause)
play_button = Button(ax_play, 'Play')
play_button.on_clicked(ex.play)

plt.show()
Dunes
  • 37,291
  • 7
  • 81
  • 97
0

Something like this may work as more of an OOP approach....

class ClasName():
    # Add any variables you want to keep track of...
    def __init__(self, dataSlider, listOfMomentsVis):
        self.paseVal = False
        self.dataSlider = dataSlider
        self.listOfMomentsVis = listOfMomentsVis 

    # Your methods to manipulate the class instance variables
    def pause(self, mouse_event):
        self.pauseVal ^= True

    def play(self, mouse_event):    
        for x in range(int(self.dataSlider.val),len(self.listOfMomentsVis)):
            if self.pauseVal == True:
                # Reset the pauseVal state
                self.pauseVal ^= False
                break

            else:
                image.set_data(listOfMomentsVis[x]) 
                fig.canvas.draw()
                dataSlider.set_val(x+1)
                time.sleep(timeSleep)
                print(x)
Fury
  • 456
  • 6
  • 12