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 ).
Related
I am actually trying to create an application but I encountered a big issue
Specifically, it should check a folder filled with pictures for it's items and for each item it should create an ImageButton(already defined) in a Scollview GridLayout list. It works so far..
But when one of the displayed pictures is pressed, I want it to change the path of the picture on the main screen
This is my Python code
#This is outside the MDApp class
class ImageButton(ButtonBehavior, Image):
#Inside the MDApp class
def item_choser(self, item)
.
.
.
elif item == "car":
#It first clears the grid layout
self.root.ids.pictures_grid.clear_widgets()
#Then, the path folder of the items(pictures) is being defined
pictures = glob.glob(f"./images/items/cars/*.jpg")
#For loop created to assign
for i in pictures:
#This "z" is the number after path in format <path\02_test_golf7_tez.jpg>
#Different for every picture (01, 02, 03 etc)
z = i[-21:-19]
#Creating button with bind
z = ImageButton(source=i, allow_stretch=True, keep_ratio=True)
z.bind(on_release=lambda x:self.chosen(i)) <<--- Here is my actual problem
print(z)
#Adding it into the grid
self.root.ids.pictures_grid.add_widget(z)
def chosen(self, selectedCar):
print(selectedCar)
self.root.ids.main_image_display.source = selectedCar
This is what the path folder contains:
...cars\01_test_golf8_tez.jpg
...cars\02_test_golf7_tez.jpg
...cars\03_test_passat_te.jpg
...cars\04_test_crafter_t.jpg
All photos are rightly placed. For every " z " printed after the bind, it shows a different object in memory so until here everything is fine
But here is where my problem starts.
Let's say you want to pick the passat so you press the picture of it but somehow, whatever picture you press, the chosen function will always print "...cars\04_test_crafter_t.jpg"
How could this code be made in order to bind 1 button to it's function?
This was the closest answer found but either I don't understand how to use and integrate it in my code or it simply doesn't help [lambda x, i=i : i * x for i in range(len(pictures))] because it didn't work
This may not be the full answer, but I can't squeeze much code in a comment.
Make gala a list then append each picture to it:
gala = []
for i in pictures:
z = i[-21:-19]
tmp = ImageButton(source=i, allow_stretch=True, keep_ratio=True)
tmp.bind(on_release=lambda x, i=i:self.chosen(i)) # closure hack
self.root.ids.pictures_grid.add_widget(tmp)
gala.append(tmp) # can reference later > gala[0]
Concerning the 'hack'
The target function (lamda) does not run immediately. When defining the function, python uses late binding. With this, python stores the method address (lambda) and the parameter address (i). It does not store the value of the parameter. When the function is later called, the current (latest) value of the parameter variable is used.
Take this example. When chosen is called, i will equal 2 because that is the value of i when the loop is completed but before the function is actually called.
for i in range(3): 0-2
tmp.bind(on_release=lambda i:self.chosen(i)) # address of i is stored, not value
The hack resolves this issue by forcing early binding. Using i=i tells python there is a default value for that parameter (like def myfunc(i=5)) and early binding should be used. With early binding, the (default) parameter values are stored when the method is created, resolving the loop issue.
for i in range(3): 0-2
tmp.bind(on_release=lambda x, i=i:self.chosen(i)) # value of i is stored
The x parameter may not be needed. You can experiment to confirm.
#inclement provided a useful link in his comment: https://docs.python-guide.org/writing/gotchas/#late-binding-closures
I would assume that the problem is in the ImageButton Class - you need to check there if exactly that button was pressed.
class ImageButton(Button):
# ....
def on_touch_down(self, touch):
"""
Check which of the buttons was pressed
:param touch:
:return:
"""
if self.collide_point(*touch.pos):
print(self.source)
# You could then trigger an event from here and pass the Source/ID whatever as parameter
else:
# This Button wasn't pressed
pass
You can check that with the touch parameter of the on_touch_down function. Check with which button collider the touch.pos collides. The on_touch_down event is triggered for all ImageButtons even when only one is pressed!
I always use the Button id to identify the button - and link that with more data.
I am new to bokeh and writing a small bokeh server app, which has plot and a button. When the button is pressed, data is recalculated and plot updates. The idea is that as soon as the button pressed, it changes the color and label, also a text "calculating..." appears. When calculations are done, plot updates and the text disappears.
However, when button is pressed, it doesn't change color and the text does not appear before the calculations are done (takes several seconds). All this widget update happens after calculations. Question, is it possible to force a widget to update, like flush=True in case of print() or something similar may be?
I could not find anything in bokeh documetation. I have tried also to separate widget changes and calculations and execute them in two separate functions, but it didn't help. Setting a delay between button change and invoke of calculation function also did not help. Seems, like update on widgets only happens on exit from callback function or even later. The only thing which I did not check is CustomJS, but I don't know how to write js code for button update.
Thanks for any help!
Here is a code sample close to what I actually use:
from bokeh.plotting import figure
from bokeh.models import Button, PreText, ColumnDataSource
from bokeh.layouts import row
p = figure()
source = ColumnDataSource(data={"x":[0], "y":[0]})
p.line(x="x", y="y", source=source)
variable = False
# initialise widgets
switchButton = Button(label='Anticrossing OFF', button_type="default")
process_markup = PreText(text='Calculating...', visible=False)
def callback(arg):
global variable
global process_markup
variable = not variable
# change button style
if variable:
switchButton.update(label = 'Anticrossing ON',
button_type = 'success')
else:
switchButton.update(label = 'Anticrossing OFF',
button_type = 'default')
# show "calculating..."
process_markup.update(visible=True)
# do long calculations
x, y = calculate_data(variable)
source.data = {"x":x, "y":y}
# hide "calculating..."
process_markup.update(visible=False)
switchButton.on_click(callback)
col = column(switchButton, process_markup)
curdoc().add_root(row(col, p))
Bokeh can send data updates only when the control is returned back to the server event loop. In your case, you run the computation without every yielding the control, so it sends all of the updates when the callback is already done.
The simplest thing to do in your case is to split the callback into blocks that require synchronization and run each block in a next tick callback:
def callback(arg):
global variable
global process_markup
variable = not variable
# change button style
if variable:
switchButton.update(label = 'Anticrossing ON',
button_type = 'success')
else:
switchButton.update(label = 'Anticrossing OFF',
button_type = 'default')
# show "calculating..."
process_markup.update(visible=True)
def calc():
# do long calculations
x, y = calculate_data(variable)
source.data = {"x":x, "y":y}
# hide "calculating..."
process_markup.update(visible=False)
curdoc().add_next_tick_callback(calc)
Note however that such a solution is only suitable if you're the only user and you don't need to do anything while the computation is running. The reason is that the computation is blocking - you cannot communicate with Bokeh in any way while it's running. A proper solution would require some async, e.g. threads. For more details, check out the Updating From Threads section of the Bokeh User Guide.
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.
folks! So, thanks to you guys I was able to figure out what it was I was doing wrong in my previous script of staggering animation for selected objects in a scene. I am now on part two of this little exercise: Creating a UI for it.
This involves creating a window with a button and user input of how much the animation will be staggered by. So, instead of me putting how much the stagger should increment by (which was two in my previous script), I'd now allow the user to decide.
The script I have so far created the window, button, and input correctly, though I am having some trouble with getting the UI to properly execute, meaning when I click on the button, no error pops up; in fact, nothing happens at all to change the scene. I get the feeling it's due to my not having my increment variable in the correct spot, or not utilizing it the right way, but I'm not sure where/how exactly to address it. Any help would be greatly appreciated.
The code I have (with suggested edits) is as follows:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
print "BLAH"
Moving the comments into an answer because I think I've got it all figured out finally:
First of all, the better practice is to pass the stagger object to the button command rather than the string. so that would be:
cmds.button(label="My Life For Aiur!", command=stagger)
Secondly, the count isn't getting updated, so it stays 0 as per your third line. To update that:
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
But wait, where did fieldgroup come from? We need to pass it into the function. So go back to your button code and take out the command entirely, also saving the object to a variable:
button = cmds.button(label="My Life For Aiur!")
Now store the object for the fieldgroup when you make it:
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
Now that you have fieldgroup, you can pass that in the function for the button, like this:
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
I had to wrap the function in a lambda because we're passing fieldgroup, but if I just put stagger(fieldgroup) it would call that and pass the result of that into the command for the button
Also update stagger def with fieldgroup argument:
def stagger(fieldgroup):
One final note that won't actually affect this, but good to know:
when you shift the keyframes inside stagger you're using a DIFFERENT count variable than the one you declared as 0 up above. The outer one is global, and the inner is local scope. Generally it's best to avoid global in the first place, which fortunately for you means just taking out count = 0
Putting that all together:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
count = 0
increment = cmds.floatFieldGrp(fieldgroup, query=True, value=True)[0]
print count
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
count += increment
print "BLAH"
So I'm trying to create a dialog box that asks the user for an input (a number) with python's built-in Tkinter library. In particular, I googled that this could be easily achieved with the method simpledialog.askinteger.
In a normal tkinter.button, I have the argument "command" which allows me to call a method. This is how I first made this part of my code within the main window:
self.generate_game_button = tkinter.Button(self.main_window, text='Start!', \
command=self.create_grid)
But as I want to ask for this number in a pop up window, in tkinter.simpledialog.askinteger, there is no argument for command, so I'm left with no way of calling my create_grid method... The code looks like:
def press_newgame(self):
global a
a = tkinter.simpledialog.askinteger('Inputz', 'Enter the gameboard size')
My create_grid method basically makes a set of buttons using the inputted int... How can I achieve this using a pop up window to ask the user for a number, and then call the create grid method similar to how the tkinter.Button works?
I hope this makes sense... Thanks.
Well, this is working differently than a simple button, because askinteger is a dialog window, which is not there constantly, it has to be called, and then it will automatically return you a value -- as you expect it.
So I guess you want to do something with the given a value (you probably want to pass it to the create_grid method, so all you have to do is call the method after you got the integer value, something like this:
def press_newgame(self):
a = tkinter.simpledialog.askinteger('Inputz', 'Enter the gameboard size')
self.create_grid(a)
I'm not sure a perfectly understand your usecase. If i understand well, you have a "New game" button, and after the user pressed that button, you want to show the askinteger dialog to get the size of the grid you have to generate for the player. In this case, why you just call your your grid-creating function simply after you came back from the dialog, so like:
global a
a = tkinter.simpledialog.askinteger('Inputz', 'Enter the gameboard size')
createGrid(size=a) # or whatever your function is