calling a function which shows sub category list in a select box using observe function is working only first time - function

I have simple df have main category and its sub items. I made two select boxes to select main category and it automatically shows belonged items at the other select box using 'observe'
But it works only at the first time category selection and after I selected another category it didn't show sub items in an another select box.
If i put 'observe' inside the 'def' the function is worked as I wanted.
But this approach is troublesome if I have many other 'def' executing on observe on change.
I am also looking for the way to make this code simple without using global.
And also welcome any ideas to make this code simpler and lighter.
import pandas as pd
import ipywidgets as wg
from ipywidgets import *
from IPython.display import display, clear_output
df_list=[{'Category':'fruit','name':['banana','apple','mango']},\
{'Category':'animal','name':['lion','monkey','tiger','cat','dog']},\
{'Category':'body','name':['eyes','hands','ears','arms']},\
{'Category':'office','name':['table','computer','printer']}]
df=pd.DataFrame(df_list)
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'))
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
display(hbox)
def select_on_change(change):
if change['name'] == 'value':
global Category_box
global name_box
global hbox
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'),value=Category_box.value)
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
clear_output()
display(hbox)
#Category_box.observe(select_on_change)
Category_box.observe(select_on_change)

Firstly, thanks for the complete and clear example.
The solution is to not create new widgets in your function that you are observing. Instead just update the .options of your name_box widget. This way, no globals required.
import pandas as pd
import ipywidgets as wg
from ipywidgets import *
from IPython.display import display, clear_output
df_list=[{'Category':'fruit','name':['banana','apple','mango']},\
{'Category':'animal','name':['lion','monkey','tiger','cat','dog']},\
{'Category':'body','name':['eyes','hands','ears','arms']},\
{'Category':'office','name':['table','computer','printer']}]
df=pd.DataFrame(df_list)
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'))
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
display(hbox)
def select_on_change(change):
if change['name'] == 'value':
name_box.options = list(df[df['Category']==Category_box.value].name)[0]
Category_box.observe(select_on_change)

Related

How do I repeat a function in a for loop (with multiple entries and listboxes)?

I've created multiple entry boxes and listboxes with one code, so I have three entry boxes and three listboxes next to eachother (used a for loop for this).
But I also made a function that the listbox will be automatically updated to show words that look like what I'm typing. So if I start typing 'horse' in the entry box, I only get sentences with 'horse' in the listbox. However, the function only works on one listbox. So although I have three entry boxes and three listboxes, the functions in my code will only be executed once (in one entry/listbox).
The functions are 'check' and 'fillout'.
I think I need to work with lamdba? Or with .bind(<'Return'>)? I tried both ways, but it just doesn't work out (and I don't understand lambda). How can I repeat not only the entry- and listbox, but also the define function in it?
My code:
import tkinter as tk
from tkinter import Listbox
from tkinter import *
interface = tk.Tk()
def update(data):
my_list.delete(0, END)
for item in data:
my_list.insert(END, item)
def check(e):
typed = entry.get()
if typed == '':
data = alist
else:
data = []
for item in alist:
if typed.lower() in item.lower():
data.append(item)
update(data)
def fillout(e):
entry.delete(0, END)
entry.insert(0, my_list.get(ACTIVE))
for x in range(3):
entry = Entry(interface, width=53)
entry.bind('<KeyRelease>', check)
#?entry.bind('<Return>', check)
entry.grid(row=0, column=x, pady=20, padx=5)
my_list: Listbox = Listbox(interface, height=20, width=50)
my_list.bind("<<ListboxSelect>>", fillout)
my_list.grid(row=3, column=x, pady=20, padx=5)
update(alist)
interface.mainloop()

Image locating with pyautogui

With pyautogui I'm trying to locate a object in a screenshot that is being taken but I can't find a way
from pyautogui import *
import pyautogui
import time
import keyboard
import random
import win32api, win32con
time.sleep(2)
ims = pica = pyautogui.screenshot(region=(569,381,800,450))
iml = pyautogui.screenshot(region=(1040,295,100,30))
def click(x,y):
win32api.SetCursorPos((x,y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
#Color of center: (255, 219, 195)
while keyboard.is_pressed('q') == False:
flag = 0
pyautogui.screenshot(region=(1040,295,100,30))
ims.save(r"C:\Users\Billy Bong\Desktop\python\saveimage.png")
pica = pyautogui.screenshot(region=(569,381,800,450))
ims.save(r"C:\Users\Billy Bong\Desktop\python\saveimage2.png")
time.sleep (2)
pyautogui.locateAllOnScreen("saveimage.png", confidence=0.6)
print (cords)
pyautogui.click (cords)
time.sleep (10)
break
Here is the minimal code for locating an object on the screen, you should have the image of the object.
import pyautogui as py
import keyboard
while True:
img = py.locateOnScreen("object.png", confidence=0.9) # add path to your object image here
print(img)
if keyboard.is_pressed('q'):
break
there is something i found out about pyautogui if u gonna use locateonscreen or locateallonscreen the image u asking him to locate must be in same folder u running the script from (according to my experience from using VS-code) and idk if there is a way to tell it to set a path for the image u wanna search to (something) so for the current time make sure u include all images u search for in same folder that the script is running from for me it is (E:\coding\game) cuz i make auto bots for games i like
btw locateallonscreenn prints out a list, the "cords" but idk if "pyautogui.click (cords)
" understand that and if it is going to click all the x,y in cords or not

Bokeh: Slider is not updating results on Hbar plot

I wrote the following code for using a slider to filter and update values on a Hbar plot in Bokeh.
The plot (as shown in the picture) outputs correctly, but when I move the slider nothing happens.
I'd greatly appreciate any feedback.
import pandas as pd
from bokeh.core.properties import value
from IPython.display import display, HTML
from bokeh.plotting import figure, show
from bokeh.layouts import row, column, gridplot
from bokeh.io import output_notebook, save, curdoc
from bokeh.models import ColumnDataSource, HoverTool, DatetimeTickFormatter, FactorRange, DataTable, TableColumn, DateFormatter
from bokeh.models.widgets import Panel, Tabs, Slider
import matplotlib.pyplot as plt
xls=pd.ExcelFile(path)
test_data=pd.read_excel(xls, 'test_data')
display(test_data)
AREA counts
A 500
B 100
C 70
D 50
E 40
F 20
G 10
H 2
def myplot(doc):
source = ColumnDataSource(pd.DataFrame(data=test_data))
area_list=source.data['AREA'].tolist()[::-1]
# Creating the Bar Chart
p = figure(y_range=area_list ,plot_height=500, plot_width=500, title="Total counts per area",
x_axis_label='counts', y_axis_label='AREA')
p.hbar(y='AREA', right='counts', height=1,
line_color="black", fill_color='red',line_width=1,
source=source)
def update_plot(attr, old, new):
Number_of_counts = slider.value
new_data = test_data.loc[test_data['counts'] >=Number_of_counts]
source = ColumnDataSource(data=new_data)
# Make a slider object: slider
slider = Slider(start=1, end=100, step=1, value=1, title='counts')
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
doc.add_root(column(slider, p))
show(myplot)
You're replacing the value of the source variable, but the old source is still there, being used by all the created models.
Instead of recreating the source, try to reassign the data attribute of the old source:
# source = ColumnDataSource(data=new_data)
source.data = ColumnDataSource.from_df(new_data)

Change button name on ipywidgets

Is there a way to easily change the button name in the ipywidgets module? I am using the decorator, but cannot find in the documentation how to change the name to something other than "Run Interact". I believe I need to use the decorator since my function needs to be run on demand and depends on multiple inputs from different widgets, but I'm open to other ways of doing so as well.
import ipywidgets as widgets
from IPython.display import display
#widgets.interact_manual(number1 = widgets.Dropdown(
options=[1,2],
description='select a number'),
number2 = widgets.Dropdown(
options=[3,4],
description='select another number'))
def add_numbers(number1,number2):
return number1+number2
A bit quick and dirty but you can set
widgets.interact_manual.opts['manual_name'] = 'Your text here'
before defining your function and this should change the label name. If you have multiple interact_manual calls that need different labels you will need to change it each time.
import ipywidgets as widgets
from IPython.display import display
widgets.interact_manual.opts['manual_name'] = 'Your text here'
#widgets.interact_manual(number1 = widgets.Dropdown(
options=[1,2],
description='select a number'),
number2 = widgets.Dropdown(
options=[3,4],
description='select another number'),)
def add_numbers(number1,number2):
return number1+number2

IPython notebook widgets using interactive

I'm having trouble creating widgets in a Jupyter notebook that update when other widget values are changed. This is the code I've been playing around with:
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
from IPython.display import display
def func(arg1,arg2):
print arg1
print arg2
choice = widgets.ToggleButtons(description='Choice:',options=['A','B'])
display(choice)
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a = interactive(func,
arg1=metric,
arg2=text,
__manual=True)
def update(*args):
if choice.value == 'A':
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a.children = (metric,text)
else:
metric = widgets.Dropdown(options=['monday','tuesday','wednesday'],description='Days:')
text2 = widgets.Textarea(description='Text2:')
a.children = (metric,text2)
choice.observe(update,'value')
display(a)
The resulting widgets metric and text do change based whether A or B is selected, but the problem is that the "Run func" button goes away as soon as I change to B. I've tried adding the __manual attribute immediately before display(a), adding it within update, and several other places. How do I change the children of the widget box without overwriting the fact that I want to manually run the function?