Reputation: 911
I just asked a similar question about updating cards in Plotly Dash, but for some reason, this slightly different situation is stumping me. I have some cards on my dashboard that I want to update with the movement of a date slider. Using a drop down menu with a string type variable worked just fine, but now that we have a date value in a slider, this is giving me some trouble.
Here is some of my code:
#Import packages
import pandas as pd
import numpy as np
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import time
who_data = pd.read_csv("https://covid19.who.int/WHO-COVID-19-global-data.csv")
pops = pd.read_csv("https://gist.githubusercontent.com/curran/0ac4077c7fc6390f5dd33bf5c06cb5ff/raw/605c54080c7a93a417a3cea93fd52e7550e76500/UN_Population_2019.csv")
who_data.rename(columns={'New_cases': 'New Cases', 'Cumulative_cases': 'Cumulative Cases', 'New_deaths': 'New Deaths','Cumulative_deaths': 'Cumulative Deaths'}, inplace=True)
daterange = pd.date_range(start=who_data['Date_reported'].min(),
end=who_data['Date_reported'].max(),
freq='D')
def unixTimeMillis(dt):
''' Convert datetime to unix timestamp '''
return int(time.mktime(dt.timetuple()))
def unixToDatetime(unix):
''' Convert unix timestamp to datetime. '''
return pd.to_datetime(unix,unit='s')
def getMarks(start, end, Nth=50):
''' Returns the marks for labeling.
Every Nth value will be used.
'''
result = {}
for i, date in enumerate(daterange):
if(i%Nth == 1):
# Append value to dict
result[unixTimeMillis(date)] = str(date.strftime('%Y-%m-%d'))
return result
card1_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num1"),
html.P("Cumulative Cases for Date", className="card-text",id="card_text1")
],
style={'display': 'inline-block',
'text-align': 'center',
'color':'white',
'background-color': 'rgba(37, 150, 190)'})
card2_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num2"),
html.P("Cumulative Deaths for Date", className="card-text",id="card_text2")
],
style={'display': 'inline-block',
'text-align': 'center',
'color':'white',
'background-color': 'rgba(37, 150, 190)'})
card1 = dbc.Card(card1_body,outline=True)
card2 = dbc.Card(card2_body,outline=True)
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div([
dcc.Tabs([
dcc.Tab(label='Spread',
children=[
html.Div([
dcc.RangeSlider(id='slider',
min = unixTimeMillis(daterange.min()),
max = unixTimeMillis(daterange.max()),
value = [unixTimeMillis(daterange.min())],
marks=getMarks(daterange.min(),daterange.max())
),
dbc.Row(id="card_row",children=[dbc.Col(card1),
dbc.Col(card2)
])
])
]),
dcc.Tab(label="Top 10",
children=[
html.P('Some stuff will go here eventually.')
])
])
])
@app.callback(
Output('card_row','children'),
Input('slider','value')
)
def update_cards(date_select):
date_df = who_data[(who_data.Date_reported<=date_select)]
tot_cases = f"{date_df['New Cases'].sum():,.0f}"
tot_deaths = f"{date_df['New Deaths'].sum():,.0f}"
card1 = dbc.Card([
dbc.CardBody([
html.H4(tot_cases, className="card-title"),
html.P(f"Cumulative Cases on {date_select}")
])
],
style={'display': 'inline-block',
'width': '50%',
'text-align': 'center',
'background-color': 'rgba(37, 150, 190)',
'color':'white',
'fontWeight': 'bold',
'fontSize':20},
outline=True)
card2 = dbc.Card([
dbc.CardBody([
html.H4(tot_deaths, className="card-title"),
html.P(f"Cumulative Deaths on {date_select}")
])
],
style={'display': 'inline-block',
'width': '50%',
'text-align': 'center',
'background-color': 'rgba(37, 150, 190)',
'color':'white',
'fontWeight': 'bold',
'fontSize':20},
outline=True)
return (card1, card2)
app.run_server(host='0.0.0.0',port='8050')
When I run this, the two cards do not update and I get an error message that reads, "ValueError: Lengths must match to compare". This is referring to the line where I try to create "date_df" within the update_cards function. Doing some research on StackOverflow, I found a few examples that suggested date_select is a series of 1 value and that I need to stick a .values[0] on the end of it to select the one value - but this didn't work either. Not sure how to proceed, any help would be appreciated!
Thank you!
Upvotes: 0
Views: 1300
Reputation: 538
Well your error is because you are comparing a dataframe object to a non-scalar and that non-scalar is not the same length as your dataframe object.
This is what I think you think is going on
In [79]: df = pd.DataFrame(np.random.randint(5, 15, (10, 3)), columns=list('abc'))
In [80]: df
Out[80]:
a b c
0 6 11 11
1 14 7 8
2 13 5 11
3 13 7 11
4 13 5 9
5 5 11 9
6 9 8 6
7 5 11 10
8 8 10 14
9 7 14 13
In [81]: df[df.b > 10]
Out[81]:
a b c
0 6 11 11
5 5 11 9
7 5 11 10
9 7 14 13
But in reality you are doing something like this
df[df.b > [1,2,3,4,5,6]]
So first you need to find out date_select
is actually. These are simply my assumptions, but there is definitely issues with the way your data is formatted.
Second you should really read up on the plotly callback documentation. You are trying to recreate the entire HTML div in the callback function. The functionality of the callback feature is the just update the graphs. It would make your life a lot easier if you isolate the graph from the HTML and just update the graph.
https://dash.plotly.com/basic-callbacks
https://dash.plotly.com/advanced-callbacks
Upvotes: 1