I’m quite new to Python and Plotly Dash, currently facing issues when refactoring this dashboard to use pattern matching and circular callback.
Key Features of dashboard
User adjust weightage of each factor via Slider or Input. Each Slider and Input are synchronized
Each city have base score for each factor in excel sheet. Download excel
Index of each city is calculated by weighted average for all factors (base score * weightage)
Dashboard
Issues when refactoring:
When printing value for one factor for testing, values of all factors were printed instead of one.
As all values are triggered instead of one, unable to differentiate for weighted average calculation for each factor.
Only the highlighted components can be adjusted. For example, Slider for Smart mobility can be adjusted, which change value for corresponding input. But Input value can’t be adjusted to change slider value.
Synchronizing issue between slider-input pairs
Attempt to refactor
import dash
from dash.dependencies import Input, Output, MATCH
from dash import dcc, ctx
import dash_bootstrap_components as dbc
import dash_daq as daq
from dash import html
import plotly.express as px
import pandas as pd
df= pd.read_excel("C:\\Desktop\\Smart_City.xlsx",
sheet_name='cities')
factors = []
for i in df.columns[1:]:
factors.append(i)
PLOTLY_LOGO = "https://images.plot.ly/logo/new-branding/plotly-logomark.png"
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "Smart Cities"
# make a reuseable navitem for the different examples
nav_item = dbc.NavItem(dbc.NavLink("Link", href="https://plotly.com"))
# make a reuseable dropdown for the different examples
dropdown = dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem("Entry 1"),
dbc.DropdownMenuItem("Entry 2"),
dbc.DropdownMenuItem(divider=True),
dbc.DropdownMenuItem("Entry 3"),
],
nav=True,
in_navbar=True,
label="Menu",
)
# this example that adds a logo to the navbar brand
logo = dbc.Navbar(
dbc.Container(
[
html.A(
# Use row and col to control vertical alignment of logo / brand
dbc.Row(
[
dbc.Col(html.Img(src=PLOTLY_LOGO, height="30px")),
dbc.Col(dbc.NavbarBrand("Smart Cities Index", className="ms-2")),
],
align="center",
className="g-0",
),
href="https://plotly.com",
style={"textDecoration": "none"},
),
dbc.NavbarToggler(id="navbar-toggler2", n_clicks=0),
dbc.Collapse(
dbc.Nav(
[nav_item, dropdown],
className="ms-auto",
navbar=True,
),
id="navbar-collapse2",
navbar=True,
),
],
),
color="dark",
dark=True,
className="mb-5",
)
app.layout = html.Div([
logo,
dbc.Row(
[
dbc.Col(html.Div([
dbc.Row(dbc.Col(html.Div([
html.Label(f'{name}'),
dcc.Slider(
id={'type': 'slider','index': name},
min=0, max=100,
marks=None,
value=10,
)],
style={
'font-size': '14px',
'height': '38px',
'width': '100%',
'display': 'inline-block',
'vertical-align': 'bottom',
})
))
for name in factors]), width={"size": 3, "offset": 1}),
dbc.Col(html.Div([
dbc.Row(dbc.Col(html.Div([
html.Br(),
daq.NumericInput(
id={'type': 'num_input','index': name},
min=0, max=100, value=10, size=60,
),],
style={
'height': '38px',
'width': '100%',
'display': 'inline-block',
'vertical-align': 'bottom',
})
))
for name in factors]), width=1),
dbc.Col(html.Div(dbc.Card(html.Div([
dcc.Loading([
dcc.Graph(id="chart", )])], style={'horizontal-align': 'middle' ,'vertical-align': 'middle', 'overflowY': 'scroll'},)
, style={'height': '450px', 'display':'inline-block',})), width=7)
]
),
])
#app.callback(
[Output({'type': 'slider', 'index': MATCH}, 'value'),
Output({'type': 'num_input', 'index': MATCH}, 'value')],
inputs={
"all_inputs": {
"slider": Input({'type': 'slider','index': MATCH}, "value"),
"num_input": Input({'type': 'num_input','index': MATCH}, "value")
}
}
)
def callback(all_inputs):
c = ctx.args_grouping.all_inputs
value_Smart_Mobility = 10 #Quick fix to prevent unbounded error
value_Smart_Mobility = c.num_input.value if c.num_input.id.index == 'Smart_Mobility' else
c.slider.value
print(value_Smart_Mobility) #Values for all factors were printed out, instead of one only
value_Smart_Mobility = c.slider.value if c.slider.id.index == 'Smart_Mobility' else
c.num_input.value
print(value_Smart_Mobility) #Values for all factors were printed out, instead of one only
return value_Smart_Mobility, value_Smart_Mobility
if __name__== '__main__':
app.run_server(debug=True)
Original long callback version
The original long callback version worked but its too lengthy and require manual change in App Callback whenever column names in excel changes.
import dash
from dash.dependencies import Input, Output, MATCH
from dash import dcc, ctx
import dash_bootstrap_components as dbc
import dash_daq as daq
from dash import html
import plotly.express as px
import pandas as pd
# Extract column names from excel file
df= pd.read_excel("C:\\Desktop\\Smart_City.xlsx", sheet_name='cities')
factors = []
for i in df.columns[1:]:
factors.append(i)
PLOTLY_LOGO = "https://images.plot.ly/logo/new-branding/plotly-logomark.png"
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "Smart Cities"
# make a reuseable navitem for the different examples
nav_item = dbc.NavItem(dbc.NavLink("Link", href="https://plotly.com"))
# make a reuseable dropdown for the different examples
dropdown = dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem("Entry 1"),
dbc.DropdownMenuItem("Entry 2"),
dbc.DropdownMenuItem(divider=True),
dbc.DropdownMenuItem("Entry 3"),
],
nav=True,
in_navbar=True,
label="Menu",
)
# this example that adds a logo to the navbar brand
logo = dbc.Navbar(
dbc.Container(
[
html.A(
# Use row and col to control vertical alignment of logo / brand
dbc.Row(
[
dbc.Col(html.Img(src=PLOTLY_LOGO, height="30px")),
dbc.Col(dbc.NavbarBrand("Smart Cities Index", className="ms-2")),
],
align="center",
className="g-0",
),
href="https://plotly.com",
style={"textDecoration": "none"},
),
dbc.NavbarToggler(id="navbar-toggler2", n_clicks=0),
dbc.Collapse(
dbc.Nav(
[nav_item, dropdown],
className="ms-auto",
navbar=True,
),
id="navbar-collapse2",
navbar=True,
),
],
),
color="dark",
dark=True,
className="mb-5",
)
# Using list comprehension to create slider and input. ID's Index is column name from
dataframe/excel
app.layout = html.Div([
logo,
dbc.Row(
[
dbc.Col(html.Div([
dbc.Row(dbc.Col(html.Div([
html.Label(f'{name}'),
dcc.Slider(
id={'type': 'slider','index': name},
min=0, max=100,
marks=None,
value=10,
)],
style={
'font-size': '14px',
'height': '38px',
'width': '100%',
'display': 'inline-block',
'vertical-align': 'bottom',
})
))
for name in factors]), width={"size": 3, "offset": 1}),
dbc.Col(html.Div([
dbc.Row(dbc.Col(html.Div([
html.Br(),
daq.NumericInput(
id={'type': 'num_input','index': name},
min=0, max=100, value=10, size=60,
),],
style={
'height': '38px',
'width': '100%',
'display': 'inline-block',
'vertical-align': 'bottom',
})
))
for name in factors]), width=1),
dbc.Col(html.Div(dbc.Card(html.Div([
dcc.Loading([
dcc.Graph(id="chart", )])], style={'horizontal-align': 'middle' ,'vertical-align': 'middle', 'overflowY': 'scroll'},)
, style={'height': '500px', 'display':'inline-block',})), width=7)
]
),
])
# Long callback listing every input and output. Trying to use pattern matching, so that index
wont be hardcoded
#app.callback(
[Output({'type': 'slider','index': "Smart_Mobility"}, "value"),
Output({'type': 'num_input','index': "Smart_Mobility"}, "value"),
Output({'type': 'slider','index': "Smart_Environment"}, "value"),
Output({'type': 'num_input','index': "Smart_Environment"}, "value"),
Output({'type': 'slider','index': "Smart_Government"}, "value"),
Output({'type': 'num_input','index': "Smart_Government"}, "value"),
Output({'type': 'slider','index': "Smart_Economy"}, "value"),
Output({'type': 'num_input','index': "Smart_Economy"}, "value"),
Output({'type': 'slider','index': "Smart_People"}, "value"),
Output({'type': 'num_input','index': "Smart_People"}, "value"),
Output({'type': 'slider','index': "Smart_Living"}, "value"),
Output({'type': 'num_input','index': "Smart_Living"}, "value"),
Output("chart", "figure")],
[Input({'type': 'slider','index': "Smart_Mobility"}, "value"),
Input({'type': 'num_input','index': "Smart_Mobility"}, "value"),
Input({'type': 'slider','index': "Smart_Environment"}, "value"),
Input({'type': 'num_input','index': "Smart_Environment"}, "value"),
Input({'type': 'slider','index': "Smart_Government"}, "value"),
Input({'type': 'num_input','index': "Smart_Government"}, "value"),
Input({'type': 'slider','index': "Smart_Economy"}, "value"),
Input({'type': 'num_input','index': "Smart_Economy"}, "value"),
Input({'type': 'slider','index': "Smart_People"}, "value"),
Input({'type': 'num_input','index': "Smart_People"}, "value"),
Input({'type': 'slider','index': "Smart_Living"}, "value"),
Input({'type': 'num_input','index': "Smart_Living"}, "value")]
)
# Circular callback to synchronize each slider to input. Iterate to calculate weighted average
Index for chart.
def callback(slider_Smart_Mobility, num_input_Smart_Mobility, slider_Smart_Environment,
num_input_Smart_Environment, slider_Smart_Government, num_input_Smart_Government,
slider_Smart_Economy, num_input_Smart_Economy, slider_Smart_People, num_input_Smart_People,
slider_Smart_Living, num_input_Smart_Living):
weightage = []
value_Smart_Mobility = num_input_Smart_Mobility if ctx.triggered_id == {'type':
'num_input','index': "Smart_Mobility"} else slider_Smart_Mobility
weightage.append(value_Smart_Mobility / 100 )
value_Smart_Environment = num_input_Smart_Environment if ctx.triggered_id == {'type':
'num_input','index': "Smart_Environment"} else slider_Smart_Environment
weightage.append(value_Smart_Environment / 100 )
value_Smart_Government = num_input_Smart_Government if ctx.triggered_id == {'type':
'num_input','index': "Smart_Government"} else slider_Smart_Government
weightage.append(value_Smart_Government / 100 )
value_Smart_Economy = num_input_Smart_Economy if ctx.triggered_id == {'type':
'num_input','index': "Smart_Economy"} else slider_Smart_Economy
weightage.append(value_Smart_Economy / 100 )
value_Smart_People = num_input_Smart_People if ctx.triggered_id == {'type':
'num_input','index': "Smart_People"} else slider_Smart_People
weightage.append(value_Smart_People / 100 )
value_Smart_Living = num_input_Smart_Living if ctx.triggered_id == {'type':
'num_input','index': "Smart_Living"} else slider_Smart_Living
weightage.append(value_Smart_Living / 100 )
city_index = []
for index, row in df.iterrows():
base_list = [int(row[i]) for i in factors]
index_list = [int(j*k) for j, k in zip(base_list, weightage)]
index = float("{:.2f}".format(sum(index_list) / sum(weightage)))
city_index.append(index)
df['Index'] = city_index
df['Index'] = pd.to_numeric(df['Index'])
df2 = df.sort_values(by=['Index'], ascending=True)
figure = px.bar(df2, x='Index', y='City', height=500, color='Index',
color_continuous_scale='inferno')
return value_Smart_Mobility, value_Smart_Mobility, value_Smart_Environment,
value_Smart_Environment, value_Smart_Government, value_Smart_Government,
value_Smart_Economy, value_Smart_Economy, value_Smart_People, value_Smart_People,
value_Smart_Living, value_Smart_Living, figure
if __name__== '__main__':
app.run_server(debug=True)
For my Dash app, I want to create a navigation bar that has links to the different pages but also additional stuff, e.g. the currently logged in user, and logos and stuff.
However, I unfortunately cannot use pages (Like in the "Navbar" example in dbc) since the WebApp has to be hosted as a single-url app inside another tool. My only option is to got with dcc.Tabs.
However, it looks to me like dcc.Tabs forces a newline behind the Tabs. I tried different things to prevent that, but nothing seems to be working. The best I got so far is the example below. How do I make it so that the text is in the same row as the Tabs element?
tabs_styles = {
'height': '44px', "width": "49%", "display":"inline-block"
}
app.layout = html.Div(children=[
html.Div(children=[
dcc.Tabs(id="tabs-styled-with-inline", value='tab-1', children=[
dcc.Tab(label='Page1', value='tab-1', style=tab_style, selected_style=tab_selected_style),
dcc.Tab(label='Page2', value='tab-2', style=tab_style, selected_style=tab_selected_style),
], style=tabs_styles)]),
html.Span(children=[
" Logged in as ",
html.Strong(id="username")
], style = tabs_styles),
html.Div(children=[
# Distance to header:
html.Hr(),
html.Div(id='tabs-content-inline')
])
])
Your inline style should be applied to the parent div of your Tabs component. I made some small modifications here (look at tabs_container_styles):
tabs_styles = {"height": "44px"}
tabs_container_styles = {"width": "49%", "display": "inline-block"}
app.layout = html.Div(
children=[
html.Div(
children=[
dcc.Tabs(
id="tabs-styled-with-inline",
value="tab-1",
children=[
dcc.Tab(label="Page1", value="tab-1"),
dcc.Tab(label="Page2", value="tab-2"),
],
style=tabs_styles,
)
],
style=tabs_container_styles,
),
html.Span(
children=[" Logged in as ", html.Strong(id="username")], style=tabs_styles
),
html.Div(
children=[
# Distance to header:
html.Hr(),
html.Div(id="tabs-content-inline"),
]
),
]
)
Set the parent_style property of your Tabs component instead of the style property and move your Span component to be a child of the div containing your Tabs component.
parent_style (dict; optional): Appends (inline) styles to the top-level parent container holding both the Tabs container and the content container.
style (dict; optional): Appends (inline) styles to the Tabs container holding the individual Tab components.
https://dash.plotly.com/dash-core-components/tabs
MRE
from dash import Dash, html, dcc
tabs_styles = {"height": "44px", "width": "49%", "display": "inline-block"}
app = Dash()
app.layout = html.Div(
children=[
html.Div(
children=[
dcc.Tabs(
id="tabs-styled-with-inline",
value="tab-1",
children=[
dcc.Tab(
label="Page1", value="tab-1", style={}, selected_style={}
),
dcc.Tab(
label="Page2", value="tab-2", style={}, selected_style={}
),
],
parent_style=tabs_styles,
),
html.Span(
children=[" Logged in as ", html.Strong(id="username")], style=tabs_styles
),
]
),
html.Div(
children=[
# Distance to header:
html.Hr(),
html.Div(id="tabs-content-inline"),
]
),
]
)
if __name__ == "__main__":
app.run_server()
Inside the app.layout I have an input, and when I type some info in it, I want to change the info of the CardBody(html.H6).
Trying this way I’ve been having errors like this:
TypeError: The dash_bootstrap_components.Card component (version 0.12.0) with the ID "CardBody(children={}, id='card_title1')" detected a Component for a prop other than children
Did you forget to wrap multiple children in an array?
Prop id has value CardBody(children={}, id='card_title1')
# This is inside app.layout:
html.Div([
dcc.Dropdown(
id='dropdown',
options=[{'label':i, 'value':i} for i in df['c'].unique()],
),
html.Div(id='output')
], className='search-bar'),
html.Div(
[
dbc.Row(
[
dbc.Col(children=[
dbc.Card(dbc.CardHeader("Diretor"),
dbc.CardBody([
html.H6({}, id='card_title1', className="card-title1"),
]), inverse=True)]),
],
className="mb-4 cardsrow",
),
]
),
### Callback function:
#app.callback(
Output('card-title1', 'children'),
Input('dropdown', 'value')
)
def update_output_div(stock_slctd):
if stock_slctd in df.CNPJ_FUNDO.unique():
dff = df[df['FUNDO_CODE']==stock_slctd]
else:
dff = df[df['FUND_NAME']==stock_slctd]
name = dff.DIR.iloc[0]
return name
Instead of this:
dbc.Card(
dbc.CardHeader("Diretor"),
dbc.CardBody(
[
html.H6(
{},
id="card_title1",
className="card-title1",
),
]
),
inverse=True,
)
You should wrap multiple children in an array as the error is telling you:
dbc.Card(
children=[
dbc.CardHeader("Diretor"),
dbc.CardBody(
[
html.H6(
{},
id="card_title1",
className="card-title1",
),
]
),
],
inverse=True,
)
So I want to place my text box as shown
However, my code doesn't work and show me this instead
Heres my coding:
html.H1("Query1", style={'text-align': 'center','fontSize': 30}),
dbc.Row(
[
dbc.Col(dcc.Graph(id='graphQ', figure={}),md=8),
html.P("1"),
dbc.Col(dbc.Input(type="number", min=0, max=10, step=1),),
html.P("2"),
dbc.Col(dbc.Input(type="number", min=0, max=10, step=1)),
]),
dbc.Row(
[
html.P("1"),
dbc.Col(dbc.Input(type="number", min=0, max=10, step=1),),
html.P("2"),
dbc.Col(dbc.Input(type="number", min=0, max=10, step=1),)],
align="end",
)
])]))
Any help is appreciated!
You could do something like this:
def custom_input(paragraph_text, min_value=0, max_value=10, step=1):
return html.Div(
children=[
html.P(paragraph_text, style={"paddingRight": 10}),
dbc.Input(type="number", min=min_value, max=max_value, step=step),
],
style={"display": "flex"},
)
app.layout = html.Div(
children=[
html.H1("Query1", style={"textAlign": "center", "fontSize": 30}),
dbc.Row(
[
dbc.Col(dcc.Graph(id="graphQ", figure={}), md=8),
dbc.Col(
children=[
dbc.Row(
[
dbc.Col(custom_input("1")),
dbc.Col(custom_input("2")),
],
style={"paddingBottom": 30},
),
dbc.Row(
[
dbc.Col(custom_input("1")),
dbc.Col(custom_input("2")),
],
style={"paddingBottom": 30},
),
],
md=4,
),
]
),
]
)
I've abstracted your label / input component combination into a custom function to hopefully make the layout approach more clear and the code more reusable.
So my idea here is we only need one row with two columns. Where the first column consists of the entire graph.
The second column consists of two rows where each row contains two columns, each containing a custom input.
I want to align two Dropdown menus and a DatePickerRange horizontally. But with the following code:
import dash
import dash_core_components as dcc
import dash_html_components as html
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
style_dict = dict(width='100%',
border='1.5px black solid',
height='50px',
textAlign='center',
fontSize=25)
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(children=[
html.H1(children='Hello Dash'),
# placeholder
html.Div(style={'width': '2%', 'display': 'inline-block'}),
html.Div(
dcc.Dropdown(
id = 'start_hour',
options = [{'label': i, 'value': i} for i in list(range(0,24))],
style=style_dict,
), style={'width': '20%', 'display': 'inline-block'}),
# placeholder
html.Div(style={'width': '2%', 'display': 'inline-block'}),
html.Div(
dcc.DatePickerRange(
id='date_picker_range',
style=style_dict
), style={'width': '14%', 'display': 'inline-block', 'fontSize': 20}),
# placeholder
html.Div(style={'width': '2%', 'display': 'inline-block'}),
html.Div(
dcc.Dropdown(
id = 'end_hour',
options = [{'label': i, 'value': i} for i in list(range(0,24))],
style=style_dict
), style={'width': '20%', 'display': 'inline-block'}),
])
if __name__ == '__main__':
app.run_server(debug=False, use_reloader=False)
I got this layout:
If I zoom in I got this:
Is it possible to force the components to be aligned at the top edge, no matter how I zoom in or out?
As browser I use Firefox.
I had a problem similar to the one you are describing. I was creating a dashboard that looked like this:
dcc components without proper alignement
As you can see in the image, my dcc components, as well as their titles were not aligned correctly. I tried adding the style parameter verticalAlign and it worked as I expected. This is how the dashboard looked after adding that style parameter:
dcc components aligned
I'm attaching my dashboard Layout code so you can see where I placed the parameter mentioned:
## Dashboard layout
app.layout = html.Div( ## Master div
[
html.Div( ## Dashboard title
[
dcc.Markdown(dash_title)
]
),
html.Div( ## Select menus
[
html.Div( ## Stock select
[
dcc.Markdown(dash_stock_sel),
dcc.Dropdown(
id="select_stock",
options=[{"label": cmp, "value": tickers_base[cmp]["symbol"]} for cmp in tickers_base],
value="TSLA"
)
],
style={
"display": "inline-block",
"width": "20%"
}
),
html.Div( ## Date select dcc components
[
dcc.Markdown(dash_date_sel),
dcc.DatePickerRange(
id="select_dates",
min_date_allowed=date(2015, 1, 1),
max_date_allowed=date.today(),
initial_visible_month=date(2015, 1, 1),
end_date=date.today()
)
],
style={
"display": "inline-block",
"width": "30%",
"margin-left": "20px",
"verticalAlign": "top"
}
),
]
),
html.Div( ## Stock prices graph
[
dcc.Graph(
id="cstock_graph",
figure=stock_graph(def_company, datareader_api, def_start, def_end)
)
]
)
]
)
I hope this answer helps!