How to generate variable number of Outputs in Plotly Dash - plotly-dash

I am working on a Dash application for performance monitoring using python and plotly.
I have 18 dbc tabs .
In each tab i have a tab_content. Each tab_content has :
a dbc.Card with dbc columns (graphs embedded in the columns).
an image button for toggling between showing the graphs in a grid and showing them one on top of each other.
Each time the user presses the toggle button i want to trigger a callback that changes the following properites:
the 'xl' property of the dbc.columns for the viewed tab. The number of columns differ per tab depending on the number of charts . For example, first tab "CSSR Voce" has a button 'btn-1', and 2 dbc.columns with id's : 'g11', 'g12' (each column has a dcc.Graph embedded in it )
the image of the button . I have 2 images : 1 and 2. First time, the button loads with image 1. When user presses it switches to image 2.
the width of the dbc.Card embeding the dbc.Columns When showing graphs on top of each other i change the width to 1000. When showing the graps one NEXT to each other i change the width to 2000.
My approach was like this:
Because i have 18 toggler buttons i declared:
18 id's with {index:''} for the image and 18 id's for the buttons identifiers and wrote one single callback for all the 18 tabs using MATCH.
The problem:: I can use MATCH to reference the buttons, images and card styles because these have only one index per callback but when it comes to updating the 'xl' column i cannot use MATCH because each tab has more than one graph. So , would not know how to do a one-2-one index match in my callback.
When I write the callback I would need a variable number of outputs for updating the "xl" of each tab's dbc columns for each tab. I tried using a comprehension but cannot pass the variable 'nbr' outside the decorated function and into the decorator's arguments ( expected that not to work but tried it annyway)
The callback i wrote:
#app.callback(
[Output(f"{g}", 'xl') for g in tabs_graphs[nbr] +
[Output({'name':'card','index':MATCH}, 'style'), Output({'name':'img','index':MATCH}, 'src')],
Input({'name':'btn','index':MATCH}, 'n_clicks')
)
def grid(n_clicks):
tabs_graphs = [
['g11', 'g12'],
['g21', 'g22'],
['g31'],
['g41', 'g42', 'g43'],
['g51', 'g52'],
['g61', 'g62', 'g63'],
['g71', 'g72', 'g73'],
['g81', 'g82', 'g83', 'g84'],
['g91', 'g92', 'g93'],
['g101', 'g102', 'g103'],
['g111', 'g112', 'g113', 'g114'],
['g121', 'g122', 'g123'],
['g131', 'g132'],
['g141']
]
if (n_clicks % 2) != 0:
xl = 12
style = {'width': 1000}
src = './assets/grid2.png'
index_dict_string=str(dash.callback_context.triggered[0]['prop_id'].split('.')[0])
if index_dict_string != '':
nbr = int(json.loads(index_dict_string)['index'])
print('nbr is:', nbr)
return [xl for g in tabs_graphs[nbr+1]]+[style, src]
else:
raise PreventUpdate
else:
xl = 6
style = {'width': 2000}
src = './assets/grid1.png'
index_dict_string = str(dash.callback_context.triggered[0]['prop_id'].split('.')[0])
if index_dict_string !='':
nbr=int(json.loads(index_dict_string)['index'])
print('nbr is:',nbr)
return [xl for g in tabs_graphs[nbr+1]]+[style, src]
else:
raise PreventUpdate
the HTML structure of the tabs:
tab1_content = dbc.Card(
dbc.CardBody(
[
dbc.Row([
html.Div([
html.Button(
html.Img(src='./assets/grid1.png',className='icon',id={'name':'img','index':1}),
className='grdbutton1',
n_clicks=0,
id={'name':'btn','index':1},
)
],id='btn-grid',style={'width':'100%'})
],
),
dbc.Row(
[
dbc.Col(dcc.Graph(id='CSSR_voce_1'),id='g11', sm=12,md=12,lg=12,xl=6),
dbc.Col(dcc.Graph(id='CSSR_voce_2'),id='g12',sm=12,md=12,lg=12,xl=6),
],
no_gutters=True,
)
],
),
id={'name':'card','index':1},
className="mt-3",
style={'width':2000}
),

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()

scraping - find the last 5 score of each match - in html

i would like your help to get the last 5 score, i can't get it please help me.
from selenium import webdriver
import pandas as pd
from pandas import ExcelWriter
from openpyxl.workbook import Workbook
import time as t
import xlsxwriter
pd.set_option('display.max_rows', 5, 'display.max_columns', None, 'display.width', None)
browser = webdriver.Firefox()
browser.get('https://www.mismarcadores.com/futbol/espana/laliga/resultados/')
print("Current Page Title is : %s" %browser.title)
aux_ids = browser.find_elements_by_css_selector('.event__match.event__match--static.event__match--oneLine')
ids=[]
i = 0
for aux in aux_ids:
if i < 1:
ids.append( aux.get_attribute('id') )
i+=1
data=[]
for idt in ids:
id_clean = idt.split('_')[-1]
browser.execute_script("window.open('');")
browser.switch_to.window(browser.window_handles[1])
browser.get(f'https://www.mismarcadores.com/partido/{id_clean}/#h2h;overall')
t.sleep(5)
p_ids = browser.find_elements_by_css_selector('h2h-wrapper')
#here the code of the last 5 score of each match
I believe you can use your Firefox browser but have not tested with it. I use chrome so if you want to use chromedriver check the version of your browser and download the right one, also add it to your system path. The only thing with this approach is that it open a browser window until the page is loaded (because we are waiting for the javascript to generate the matches data). If you need anything else let me know. Good luck!
https://chromedriver.chromium.org/downloads
Known issues: Sometimes it will throw index out of range when retrieve matches data. This is something I am looking to it because it look like sometimes the xpath on each link change a little .
from selenium import webdriver
from lxml import html
from lxml.html import HtmlElement
def test():
# Here we specified the urls to for testing purpose
urls = ['https://www.mismarcadores.com/partido/noIPZ3Lj/#h2h;overall'
]
# a loop to go over all the urls
for url in urls:
# We will print the string and format it with the url we are currently checking, Also we will print the
# result of the function get_last_5(url) where url is the current url in the for loop.
print("Scores after this match {u}".format(u=url), get_last_5(url))
def get_last_5(url):
print("processing {u}, please wait...".format(u=url))
# here we get a instance of the webdriver
browser = webdriver.Chrome()
# now we pass the url we want to get
browser.get(url)
# in this variable, we will "store" the html data as a string. We get it from here because we need to wait for
# the page to load and execute their javascript code in order to generate the matches data.
innerHTML = browser.execute_script("return document.body.innerHTML")
# Now we will assign this to a variable of type HtmlElement
tree: HtmlElement = html.fromstring(innerHTML)
# the following variables: first_team,second_team,match_date and rows are obtained via xpath method(). To get the
# xpath go to chrome browser,open it and load one of the url to check the DOM. Now if you wish to check the xpath
# of each of this variables (elements in case of html), right click on the element->click inspect->the inspect
# panel will appear->the clicked element wil appear selected on the inspect panel->right click on it->Copy->Copy
# Xpath. first_team,second_team and match_date are obtained from the "title" section. Rows are obtained from the
# table of last matches in the tbody content
# When using xpath it will return a list of HtmElement because it will try to find all the elements that match our
# xpath, so that is why we use [0] (to get the first element of the list). This will give use access to a
# HtmlElement object so now we can access its text attribute.
first_team = tree.xpath('//*[#id="flashscore"]/div[1]/div[1]/div[2]/div/div/a')[0].text
print((type(first_team)))
second_team = tree.xpath('//*[#id="flashscore"]/div[1]/div[3]/div[2]/div/div/a')[0].text
# [0:8] is used to slice the string because in the title it contains also the time of the match ie.(10.08.2020
# 13:00) . To use it for comparing each row we need only (10.08.20), so we get from position 0, 8 characters ([0:8])
match_date = tree.xpath('//*[#id="utime"]')[0].text[0:8]
# when getting the first element with [0], we get a HtmlElement object( which is the "table" that have all matches
# data). so we want to get all the children of it, which are all the "rows(elements)" inside it. getchildren()
# will also return a list of object of type HtmlElement. In this case we are also slicing the list with [:-1]
# because the last element inside the "table" is the button "Mostar mas partidos", so we want to take that out.
rows = tree.xpath('//*[#id="tab-h2h-overall"]/div[1]/table/tbody')[0].getchildren()[:-1]
# we quit the browser since we do not need this anymore, we could do it after assigning innerHtml, but no harm
# doing it here unless you wish to close it before doing all this assignment of variables.
browser.quit()
# this match_position variable will be the position of the match we currently have in the title.
match_position = None
# Now we will iterate over the rows and find the match. range(len(rows)) is just to get the count of rows to know
# until when to stop iterating.
for i in range(len(rows)):
# now we use the is_match function with the following parameter: first_team,second team, match_date and the
# current row which is row[i]. if the function return true we found the match position and we assign (i+1) to
# the match_position variable. i+1 because we iterate from 0.
if is_match(first_team, second_team, match_date, rows[i]):
match_position = i + 1
# now we stop the for no need to go further when we find it.
break
# Since we only want the following 5 matches score, we need to check if we have 5 rows beneath our match. If
# adding 5 from the match position is less than the number of rows then we can do it, if not we will only get the
# rows beneath it(maybe 0,1,2,3 or 4 rows)
if (match_position + 5) < len(rows):
# Again we are slicing the list, in this case 2 times [match_position:] (take out all the rows before the
# match position), then from the new list obtained from that we do [:5] which is start from the 0 position
# and stop on 5 [start:stop]. we use rows=rows beacause when slicing you get a new list so you can not do
# rows[match_position:][:5] you need to assign it to a variable. I am using same variable but you can assign
# it to a new one if you wish.
rows = rows[match_position:][:5]
else:
# since we do not have enough rows, just get the rows beneath our position.
rows = rows[match_position:len(rows)]
# Now to get the list of scores we are using a list comprehension in here but I will explain it as a for loop.
# Before that, you need to know that each row(<tr> element in html) has 6 td elements inside it, the number 5 is
# the score of the match. then inside each "score element" we have a span element and then a strong element,
# something like
# <tr>
# <td></td>
# <td></td>
# <td></td>
# <td></td>
# <td><span><strong>1:2</strong></span></td>.
# <td></td>
# </tr>
# Now, That been said, since each row is a HtmlElement object , we can go in a for loop as following:
scores = []
for row in rows:
data = row.getchildren()[4].getchildren()[0].text_content()
# not the best way but we will get al the text content on the element, in this case the span element,
# if the string has more than 5 characters i.e. "1 : 2" then we will take as if it is i.e. "1 : 2(0 : 1)". So
# in this case we want to slice it from the 2nd character from right to left and get 5 characters from that
# position.
# using a ternary expression here, if the length of the string is equal to 5 then this is our score,
# if not then we have to slice it and get the last part, from -6 which is the white space before then 2 (in
# our example) to -1 (which is the 1 before the last ')' ).
score = data if len(data) == 5 else data[-6:-1]
scores.append(score)
print("finished processing {u}.".format(u=url))
# now we return the scores
return scores
def is_match(t1, t2, match_date, row):
# from each row we want to compare, t1,t2,match_date (this are obtained from the title) with the rows team1,
# team2 and date. Each row has 6 element inside it. Please read all the code on get_last_5 before reading this
# explanation. so the for this row, date is in position 0, team1 in 2, team2 in 3.
# <td><span>10.03.20</span></td>
date = row.getchildren()[0].getchildren()[0].text
# <td><span>TeamName</span></td> (when the team lost) or
# <td><span><strong>TeamName</strong></span></td> (when the team won)
team1element = row.getchildren()[2].getchildren()[0] # this is the span element
# using a ternary expression (condition_if_true if condition else condition_if_false)
# https://book.pythontips.com/en/latest/ternary_operators.html
# if span element have childrens , (getchildren()>0) then the team name is team1element.getchildren()[0].text
# which is the text of the strong element, if not the jsut get the text from the span element.
mt1 = team1element.getchildren()[0].text if len(team1element.getchildren()) > 0 else team1element.text
# repeat the same as team 1
team2element = row.getchildren()[3].getchildren()[0]
mt2 = team2element.getchildren()[0].text if len(team2element.getchildren()) > 0 else team2element.text
# basically we can compare only the date, but jsut to be sure we compare the names also. So, if the dates and the
# names are the same this is our match row.
if match_date == date and t1 == mt1 and t2 == mt2:
# we found it so return true
return True
# if not the same then return false
return False

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?

PyQt stacked widget not moving to next page until function ends

Hi i have a program that when a button is pressed it should move to the next stacked widget replace some text in some labels and then execute some functions but this is not working and moves to the next page when the functions completes
The code is :
QtCore.QObject.connect(self.StartBtn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.start) #Start
def nextPage(self):
current_page = self.stackedWidget.currentIndex()
i = int(current_page) + 1
self.stackedWidget.setCurrentIndex(i)
def start(self):
self.nextPage()
self.animation()
self.runFunctions()
def runFunctions(self):
try:
self.DbLabel.setText(_translate("MainWindow", "Checking Database", None))
if checkDb == True:
self.DbLabel.setText(_translate("MainWindow", "Checking Database ", None))
self.checkDbFun()
self.DbLabel.setText(_translate("MainWindow", "Database checked", None))
else:
self.checkedDbImg.setPixmap(QtGui.QPixmap(_fromUtf8("Files\\x.png")))
self.DbLabel.setText(_translate("MainWindow", "Database not checked", None))
except Exception as e:
self.AlertMessage(e)
def animation(self):
self.LoadingGif = QtGui.QLabel(MainWindow)
movie = QtGui.QMovie("Files\\loading.png")
self.LoadingGif.setMovie(movie)
self.LoadingGif.setAlignment(QtCore.Qt.AlignCenter)
self.gridLayout_2.addWidget(self.LoadingGif, 4, 1, 1, 1)
movie.start()
So what i want is to press StartBtn then move to next stacked widget page load the animation image and then run the functions
You probably need to let Qt process events in order for the tab change to take effect. You could do that two ways:
insert a qApp.processEvents() between the animation() and runFunctions() (qApp is in PyQt5.QtWidgets)
call runFunctions() via a single-shot timer: QTimer.singleShot(0, runFunctions), which will schuedule runFunctions via the event loop, so any pending events will first be processed (because runFunctions() is the latest added), then runFunctions() will get called. If you actually have params for runFunctions(), use a lambda.
I favor the first approach because I find it more clearly indicates what is happening (events need to be processed), but I recommend also adding a comment on that line that "so stack tab can change".
BTW you should be use the new-style notation for signals-slot connections, much cleaner, of the form "signal.connect(slot)":
self.StartBtn.clicked.connect(self.start)
So for approach #1 your code would look like this:
from PyQt5.QtWidgets import qApp
...
self.StartBtn.clicked.connect(self.start)
...
def start(self):
self.nextPage()
self.animation()
qApp.processEvents()
self.runFunctions()
...

Jython - anyone got an idea why this Action isn't doing what I want?

This is about unit testing (using Python's unittest module). I'm trying to implement, programmatically, the user's pressing "F2" to start editing the cell of a JTable.
The utility method "run_in_edt" wraps the passed method in a Runnable and then runs it using invokeAndWait, rather than invokeLater.
def test_can_edit_table_date(self):
main_frame = FTCase2.app.main_frame
dates_table = main_frame.dates_table
def start_editing():
dates_table.requestFocus()
f2_key_stroke = javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent.VK_F2, 0 )
im = dates_table.getInputMap( javax.swing.JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT )
action_value = im.get( f2_key_stroke )
self.assertEqual( action_value, 'startEditing' )
am = dates_table.actionMap
self.f2_action = am.get( action_value )
self.assertIsNotNone( self.f2_action )
sel_row = dates_table.selectedRow
self.assertNotEqual( sel_row, -1 )
self.assertTrue( dates_table.isCellEditable( sel_row, 0 ))
self.start_editing_action_event = java.awt.event.ActionEvent( dates_table,
java.awt.event.ActionEvent.ACTION_FIRST, 'X' )
self.f2_action.actionPerformed( self.start_editing_action_event )
# dates_table.editCellAt( sel_row, 0 )
# self.assertTrue( dates_table.editing )
_utils.run_in_edt( start_editing )
# time.sleep( 1 )
def write_string_in_cell_editor():
self.assertTrue( dates_table.editing )
cell_editor = dates_table.cellEditor
self.assertIsNotNone( cell_editor )
cell_value = cell_editor.cellEditorValue
cell_editor.component.text = "mouse"
self.f2_action.actionPerformed( self.start_editing_action_event)
_utils.run_in_edt( write_string_in_cell_editor )
The problem: "dates_table.editing" always comes out false... and getting the cell editor returns None. I have also tried putting a sleep between these two Runnables, just in case it was a question of "events having to bubble up/down"...
NB I also tried with a more sensible value as the 3rd param of ActionEvent, such as action_value (i.e. 'startEditing'). No joy.
I can of course do:
dates_table.editCellAt( sel_row, 0 )
... with this uncommented, what's interesting is that, in the second method here, I set the JTextField's ("editor delegate") text to "mouse", and then "press F2" by using the action.actionPerformed... and... it works, in the sense that in my table cell renderer I allow only dates values or None, not strings, so an AssertionError is raised. Meaning that I have managed to simulate an F2 key press (NB although the name of this action is "startEditing", it also stops an editing session, in real life as in testing).
... I could content myself with using editCellAt, and having ascertained that F2 has the right entry in the right InputMap, and that the value is pops out is an Action (could be checked) with the name "startEditing", which is proven to be capable of ending an edit, I could just content myself with that.
But I so hate it when my understanding is revealed to be less than good! I want to know WHY this doesn't work...
Found the answer and put it here for reference.
My requestFocus() action on the JTable did indeed leave selection on the right row, but without any selection of the (single) column. Even when running as normal (not testing) the JTable column did not initially respond to F2. It was puzzling to me why the cell was not initially surrounded by a heavy black border. The answer was therefore to put this line after the requestFocus line:
dates_table.setColumnSelectionInterval( 0, 0 )