Hello,
Dash is an awesome tool, but for the first time I hit the wall with a problem, I can’t find a solution to. Python (and Dash) are just my hobby, so sorry in advance, if my problem is a stupid one. The same goes for the code itself
Essentialy, I wrote a really simple and basic app, which downloads a xml file with election data and generates several tables from the data in that file. Since the xml file will be updated once per minute, I used Interval component to re-download the file and generate an updated html table output.
Now, it would be nice, if rows, whose data has changed since last download, had a simple animation - short “blink”.
I created an local .css file in assets folder and so on, as specified in Dash documentation.
When I first run the app and generate output tables, the animation will run.
However, when automatic refresh via Interval component is triggered, animation won’t run.
I am out of ideas, any help, if possible, would be greatly appreciated.
Code (don’t want to bother with all of it, but if needed, I will post all of it, no problem):
CSS bit:
.row_normal{
color: inherit;
animation-name: row_change_notify;
animation-duration: 4s;
animation-timing-function: ease-in;
}
@keyframes row_change_notify {
0% {background-color: #FAFAFA}
25% {background-color: #81F79F}
50% {background-color: #2EFE64}
75% {background-color: #81F79F}
90% {background-color: #CEF6D8}
}
callback function (now - for testing, the animation should run for all rows each time the callback is fired, no matter, whether some data has changed or not):
@app.callback(
Output(component_id = 'outputWrap', component_property = 'children'),
[Input(component_id = 'BttnShowOutput', component_property = 'n_clicks'),
Input(component_id = 'updateButton', component_property = 'n_intervals')],
[State(component_id = 'NUTSselectorDrop', component_property = 'value'),
State(component_id = 'OBECSelectorDrop', component_property = 'value')]
)
def generate_output(clicks, n, nuts, obec):
if ((clicks > 0) and (nuts is not None) and (obec is not None) and (n == 0)):
print('manually firing callback "generate_output"')
return get_OBEC_output(nuts, obec)
elif ((clicks > 0) and (n > 0) and (nuts is not None) and (obec is not None)):
print('refreshing - firing callback "generate_output"')
save_xml(nuts)
return get_OBEC_output(nuts, obec)
else:
return None
Function ‘save_xml’ :
def save_xml(value):
'''saves grabbed xml data into xml file onto disc.'''
try:
print('grabbing selected xml data from web')
grab = requests.get(LINK, params={'datumvoleb' : LINK_DATE, 'nuts' : value})
print('saving selected xml data into xml file')
with open(op.join(ASSETS_PATH, 'xml_data_temp.xml'), 'w', encoding = 'utf-8') as file:
file.write(grab.text)
except Exception as e:
print('Error in f. "save_xml": ' + str(e))
Function ‘get_OBEC_output’ code:
def get_OBEC_output(nuts_code, obec_name):
'''generates output for Dash dynamic component - HTML tables'''
try:
with open(op.join(ASSETS_PATH, 'xml_data_temp.xml'), 'r', encoding='utf-8') as file:
parser = ET.XMLParser(encoding = 'utf-8')
tree = ET.parse(file, parser)
root = tree.getroot()
obce = root.findall("{http://www.volby.cz/kv/}OBEC")
obec = root.find("*[@NAZEVZAST='{}']".format(obec_name))
strany = obec.findall(".//{http://www.volby.cz/kv/}VOLEBNI_STRANA")
data = []
data_obec = []
zastupitel_data = []
for obec in obce:
nazev = obec.attrib['NAZEVZAST']
ucast = obec.find(".//{http://www.volby.cz/kv/}UCAST")
zpracovano = ucast.attrib['OKRSKY_ZPRAC_PROC']
ucast_volicu = ucast.attrib['UCAST_PROC']
data_obec.append({'Název obce' : nazev,
'Zpracováno hlasů (v %)' : zpracovano,
'Účast voličů (v %)' : ucast_volicu}
)
for strana in strany:
nazev = strana.attrib['NAZEV_STRANY']
hlasy = strana.attrib['HLASY_PROC']
kand_pocet = strana.attrib['KANDIDATU_POCET']
zast_pocet = strana.attrib['ZASTUPITELE_POCET']
zast_proc = strana.attrib['ZASTUPITELE_PROC']
data.append({'Název strany' : nazev,
'Počet hlasů (v %)' : hlasy,
'Počet kandidátů' : kand_pocet,
'Počet zastupitelů' : zast_pocet,
'Počet zastupitelů (v %)' : zast_proc}
)
zastupitele = strana.findall(".//{http://www.volby.cz/kv/}ZASTUPITEL")
for zastupitel in zastupitele:
strana_zast = nazev
jmeno = zastupitel.attrib['JMENO']
prijmeni = zastupitel.attrib['PRIJMENI']
hlasy_abs = zastupitel.attrib['HLASY']
hlasy_proc = zastupitel.attrib['HLASY_PROC']
zastupitel_data.append({'Jméno' : jmeno,
'Příjmení' : prijmeni,
'Hlasy (absolutní počet)' : hlasy_abs,
'Hlasy (v %)' : hlasy_proc,
'Strana' : strana_zast}
)
print('printing output')
return [html.Div(id = 'tableOKRESwrap',
children = [
html.Table(
[html.Tr(
[html.Th(item) for item in data_obec[0]]
)] +
[html.Tr(
[html.Td(each) for each in list(row.values())],
className = 'row_normal',
# style = blink_style
) for row in sorted(data_obec, key = lambda element: float(element['Účast voličů (v %)']), reverse = True)]
)],
style = {'width' : '50%', 'float' : 'left', 'margin' : '10px'}
),
html.Div(id = 'tableOBECwrap',
children = [
html.Div(
html.Table(
[html.Tr(
[html.Th(item) for item in data[0]]
)] +
[html.Tr(
[html.Td(each) for each in list(row.values())],
className = 'row_normal'
) for row in sorted(data, key = lambda element: int(element['Počet zastupitelů']), reverse = True)]
),
style = {'margin' : '10px'}
),
html.Div(
html.Table(
[html.Tr(
[html.Th(item) for item in zastupitel_data[0]]
)] +
[html.Tr(
[html.Td(item) for item in list(row.values())],
className = 'row_normal',
) for row in sorted(zastupitel_data, key = lambda element: int(element['Hlasy (absolutní počet)']), reverse = True)]
),
style = {'margin' : '10px'}
)
],
style = {'width' : '50%', 'align-items' : 'top', 'margin' : '0px'}
)
]
except Exception as e:
print('Error in f. "get_OBEC_output": ' + str(e))