Creating matplotlib widget callback inside a function - python

I am using python 2.7 on Windows.
I have a function which creates a figure with a CheckButtons widget, and it also includes the definition of the button's callback. When I call the function once, everything is OK, but when I call it more than once, the buttons stops responding, as follows:
If the figure is created using plt.subplots(), none of the buttons respond.
If the figure was created using plt.figure(), the behavior is inconsistent; sometimes only the 1st created button responds, and sometimes both respond.
My guess is that is has to do with the scope of the callback, but I couldn't pinpoint the problem using trial-and-error.
Sample code:
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
def create_button():
plt.subplots() # or: plt.figure()
rax = plt.axes([0.2, 0.2, 0.2, 0.2])
check = CheckButtons(rax, ['on'], [True])
def callback(label):
check.labels[0].set_text('on' if check.lines[0][0].get_visible() else 'off')
plt.draw()
check.on_clicked(callback)
create_button()
#create_button() # uncomment to reproduce problem
plt.show()

It turns out the problem was that the CheckButtons instance created inside the function no longer exists after the function returns.
The solution I came up with was to keep a list in the scope where the function is called (I used a static variable in a class), and append the instance to this list from within the function. This way the CheckButtons instance still exists when the function exits. In order for that list to not grow more than needed, I also wrote a function which deletes the corresponding instance from the list, and registered this function as a callback for the event of the figure being closed by the user.
I will be happy to hear comments on my solution, or suggestions for more Pythonish solution, if such a solution exists.

I think also if you return check at the end of the function this will work to keep the button alive on exit.

Related

Saving slider value when button is clicked in matplotlib widget

Similar to this question, I would like to save the value of a matplotlib slider when the button is clicked. Printing the value to the console is easy with print(), however I can't figure out how to save it to a variable. This is what I have tried, but it returns a value of zero before the user does anything.
def myFunction():
fig, ax = plt.subplots()
ax_slider = plt.axes([0.25, 0.1, 0.65, 0.03])
lag_slider = Slider(ax=ax_slider, label='lag (s)', valmin=-15, valmax=15, valinit=0)
def update(val):
lag = lag_slider.val
lag_slider.on_changed(update)
button_ax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(button_ax, 'Set Lag')
def set_lag(val):
lag = lag_slider.val
print(lag) # this prints the lag value to the console, I want to return it from the function
return lag
lag = button.on_clicked(set_lag)
return lag # this executes before the button is clicked
When you passing a function to on_clicked, the function is not executed yet, it runes whenever you click on the widget that you are using on_clicked on it.
This is the difference between calling a function, like set_lag() or just passing it, like set_lag ( Notice the parentheses ).
And actually somewhere in the source codes of on_clicked function, it called the function like this:
func(event)
And in this flow, your set_lag is going to be executed.
So what happens that you can print the value, but the lag variable that you return is different? There are these reasons:
You have to put your logic inside set_lag function, and do what you want to do with slider value in it.
Event functions are not going to return, because the returned value is not considered anywhere. if you look at the matplotlib source code, you see that the function is just called:
for cid, func in self.observers.items():
func(event)
The value that is returned by on_click function is just an ID of the callback function you attached, to make you able to disconnect it, so it's not equal to the lag value that you are returning from your set_lag function.
So if you need it's value, you can store it into a global variable or something like that, but it doesn't help that much, becauseyou have to get noticed when the slider is clicked, by the event function you passed.
For more information, look at for "callback functions" in python ( and other languages ).

Guizero's TextBox.value stops updating after pyplot.figure() is called

I want to create simple GUI using guizero library, but I found out that TextBox.value stops updating when matplotlib.pyplot.figure() is called.
I have not tried almost anything since I have no idea how to solve it. I just found the problem.
from guizero import App, TextBox
import matplotlib.pyplot as plt
plt.figure()
def print_value():
print(text_box.value)
app = App()
text_box = TextBox(app, command=print_value)
app.display()
I want to use TextBox as input for values (floats). If the plt.figure() is not in the code, I just enter the value in the UI and by calling text_box.value I can read the value (and print it immediately as in the example). However, I also want to use matplotlib.pyplot to plot the data. The problem is when the plt.figure() is in the code the text_box.value stops being updated. I can enter whatever I want in the UI but the text_box.value remains unchanged (it is either empty string as in this case or just default string value if I define it). Is there anything I'm doing wrong or is it a bug?

Showing plots if checkbox is checked, on python (with PyQt4)

I'm brand new to Python and I'm trying to make my first program with PyQt4. My problem is basically the following: I have two checkboxes (Plot1 and Plot2), and a "End" push button, inside my class. When I press End, I would like to see only the plots that the user checks, using matplotlib. I'm not being able to do that. My first idea was:
self.endButton.clicked.connect(self.PlotandEnd)
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
def PlotandEnd(self)
plot1=self.Plot1()
pyplot.show(plot1)
plot2=self.Plot2()
pyplot.show(plot2)
def Plot1(self)
plot1=pyplot.pie([1,2,5,3,2])
return plot1
def Plot2(self)
plot2=pyplot.plot([5,3,5,8,2])
return plot2
This doesn't work, of course, because "PlotandEnd" will plot both figures, regardless of the respective checkbox. How can I do what I'm trying to?
Wrap the plot creation in an if statement that looks at the state of the check boxes. For example:
def PlotandEnd(self)
if self.plot1Checkbox.isChecked():
plot1=self.Plot1()
pyplot.show(plot1)
if self.plot2Checkbox.isChecked():
plot2=self.Plot2()
pyplot.show(plot2)
You also don't need the following lines:
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
This does nothing useful at the moment! Qt never uses the return value of your PlotX() methods, and you only want things to happen when you click the End button, not when you click a checkbox. The PlotX() methods are only currently useful for your PlotandEnd() method.

Simple animation with Tkinter Python

I've searched for a simple animation code with Tkinter but I've found very different examples and I can't understand the correct way to write an animation.
Here my working code to display a simple moving circle:
import tkinter as tk
import time
root=tk.Tk()
canvas=tk.Canvas(root,width=400,height=400)
canvas.pack()
circle=canvas.create_oval(50,50,80,80,outline="white",fill="blue")
def redraw():
canvas.after(100,redraw)
canvas.move(circle,5,5)
canvas.update()
canvas.after(100,redraw)
root.mainloop()
In this code I can't correctly understand: how the after method works, where correctly put the update and the move method (before after method ?), is there another way to write an animation code? may you post me another example and comment the code please?
Thanks :)
Calling update
You should not call canvas.update(). As a general rule of thumb you should never call update. For a short essay on why, see this essay written by one of the original developers of the underlying tcl interpreter.
If you take out the call to canvas.update(), you have the proper way to do animation in a tkinter program.
Calling after to start the animation
You don't need to call after immediately before calling root.mainloop(). This works just as well:
...
redraw()
root.mainloop()
The choice to use or not use after in this specific case is dependent on if you want the animation to start immediately (possibly even before the widget is visible) or if you want it to happen after a short delay (possibly after the widget is made visible)
How after works
mainloop is nothing more than an infinite loop that checks the event queue for events. When it finds an event, it pops it off of the list and processes it. after is nothing more than making a request that says "in 100 ms, please add a new event to the queue". When the time limit expires, an event is added to the queue that says, in effect, "run this command". The next time the loop checks for events, it sees this event, pulls it off of the queue, and runs the command.
When you call after from within a method that itself was called by after, you're saying in effect "wait 100ms and do it again", creating an infinite loop. If you put the call to after before moving the object, you're saying "every 100ms run this function". If you put it after you're saying "run this function 100 ms after the last time it was run". The difference is very subtle and usually not perceptible unless your function takes a long time to run.
my code is:
from tkinter import *
import time
tk = Tk()
płótno = Canvas(tk, width=500, height=500)
płótno.pack()
płótno.create_polygon(10,10,10,70,70,10,fill="blue",outline="black")
for x in range(0,51):
płótno.move(1,5,0)
płótno.update()
rest(0.05)
płótno means canvas

Matplotlib canvas drawing

Let's say I define a few functions to do certain matplotlib actions, such as
def dostuff(ax):
ax.scatter([0.],[0.])
Now if I launch ipython, I can load these functions and start a new figure:
In [1]: import matplotlib.pyplot as mpl
In [2]: fig = mpl.figure()
In [3]: ax = fig.add_subplot(1,1,1)
In [4]: run functions # run the file with the above defined function
If I now call dostuff, then the figure does not refresh:
In [6]: dostuff(ax)
I have to then explicitly run:
In [7]: fig.canvas.draw()
To get the canvas to draw. Now I can modify dostuff to be
def dostuff(ax):
ax.scatter([0.],[0.])
ax.get_figure().canvas.draw()
This re-draws the canvas automatically. But now, say that I have the following code:
def dostuff1(ax):
ax.scatter([0.],[0.])
ax.get_figure().canvas.draw()
def dostuff2(ax):
ax.scatter([1.],[1.])
ax.get_figure().canvas.draw()
def doboth(ax):
dostuff1(ax)
dostuff2(ax)
ax.get_figure().canvas.draw()
I can call each of these functions, and the canvas will be redrawn, but in the case of doboth(), it will get redrawn multiple times.
My question is: how could I code this, such that the canvas.draw() only gets called once? In the above example it won't change much, but in more complex cases with tens of functions that can be called individually or grouped, the repeated drawing is much more obvious, and it would be nice to be able to avoid it. I thought of using decorators, but it doesn't look as though it would be simple.
Any ideas?
Why doesn't my answer to this SO question of yours about "refresh decorator" make it simple? I showed exactly what to do what you're again requesting here (by keeping a count of nestings -- incidentally, one that's also thread-safe) and you completely ignored my answer... peculiar behavior!-)

Categories

Resources