I am trying to build a basic webpage in Dash and Plotly, using callbacks to deliver stock graphs based on the pathname.
I would also like the graph to have a callback with radio buttons that specify the time period of data to display.
The graphs work fine however I would also like other pages to do other things and return different html but because I have specified the callback_layout in the @app.layout, it shows up on every page.
However, If I remove the callback_layout from @app.layout, I get an error in Dash saying "A nonexistent object was used in an Input
of a Dash callback an multipage app".
How would I solve this? I want the callback radio buttons under 'stock-period' to only show up when I need to pull up the Plotly graph
import dash
import dash_bootstrap_components as dbc
from dash import html
from dash import dcc
import plotly.express as px
from dash.dependencies import Input, Output, State
import pandas as pd
import yfinance as yf
external_stylesheets = [dbc.themes.CYBORG] #DARKLY
app = dash.Dash(__name__, external_stylesheets=external_stylesheets, suppress_callback_exceptions=True)
# styling the sidebar
SIDEBAR_STYLE = {
"position": "fixed",
"top": 0,
"left": 0,
"bottom": 0,
"width": "16rem",
"padding": "2rem 1rem",
# "background-color": "#4C4C4C",
}
# padding for the page content
CONTENT_STYLE = {
"margin-left": "18rem",
"margin-right": "2rem",
"padding": "2rem 1rem",
"background-color": "#060606"
}
sidebar = html.Div(
[
html.H2("Sidebard / Stocks", className="display-4"),
html.Hr(),
html.P(
"Number of students per education level", className="lead"
),
dbc.Nav(
[
dbc.NavLink("Home", href="/", active="exact"),
dbc.NavLink("Test", href="/test", active="exact"),
dbc.NavLink("NVDA", href="/NVDA", active="exact"),
dbc.NavLink("MSFT", href="/MSFT", active="exact"),
dbc.NavLink("AAPL", href="/AAPL", active="exact"),
],
vertical=True,
pills=True,
),
],
style=SIDEBAR_STYLE,
)
content = html.Div(id="page-content", children=[], style=CONTENT_STYLE) # Graph goes inside children via call back
callback_layout = html.Div([
dcc.RadioItems(
id='period-selector',
options = [
{'label': '1 mth', 'value': '1mo'},
{'label': '3 mth', 'value': '3mo'},
{'label': '6 mth', 'value': '6mo'},
{'label': '1 yr', 'value': '1y'},
{'label': '5 yr', 'value': '5y'}
],
value = "6mo",
#labelStyle is done in CSS
labelStyle={
'textAlign': 'center',
'display': 'inline-block',
'color': 'white',
'font-family': 'Arial',
'font-size' : '15px',
'padding-right': '30px'
},
style={
'textAlign': 'center'
}
)
])
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
sidebar,
content,
callback_layout
])
homepage_layout = html.Div([
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
])
""" Plotly graph """
@app.callback(
Output("page-content", "children"),
Input("url", "pathname"),
Input('period-selector', 'value')
)
def render_page_content(pathname, period_selector):
""" App routing from sidebar to content depending on path """
if pathname == '/':
return homepage_layout
if pathname == '/test':
return 'Test'
else: # If pathname isn't defined above, assume is stock ticker and calls graph below
interval = '1d'
quote = yf.Ticker(pathname)
hist = quote.history(period_selector, interval)
df = hist.round(decimals=2)
last_price = df.iloc[-1, 0] # return first row (-1)
#Performance over period calc
performance_calc = ((df.iloc[-1, 0] / df.iloc[0, 0]) - 1) * 100
if performance_calc >= 0:
performance = '+' + str(round(performance_calc,2))
else:
performance = str(round(performance_calc,2))
#Define color of performance %
performance_int = (round(performance_calc,2))
if performance_int >= 0:
perf_color = 'lime'
else:
perf_color = 'red'
fig = px.line(df,
x=df.index, y=df["Close"],
title='NVDA',
template="plotly_dark",
color_discrete_sequence=['lime'],
labels = {'Date': ''}
)
if interval == '1mo':
d_tick='604800000' #7 days in milliseconds. Datetime format requires ms input.
elif interval == '5y':
d_tick = 'M12'
else:
d_tick='M1'
fig.update_xaxes(
dtick=d_tick,
showgrid=False,
)
fig.update_yaxes(
showgrid=False,
visible=False
)
#Last price annotation
fig.add_annotation(dict(font=dict(color='white',size=40, family='Arial Black'),
x=0,
y=1.2,
showarrow=False,
text=str(last_price),
textangle=0,
xanchor='left',
xref="paper",
yref="paper")
)
#Performance annotation
fig.add_annotation(dict(font=dict(color=perf_color,size=25, family='Arial'),
x=0.14,
y=1.12,
showarrow=False,
text=str(performance) + '%',
textangle=0,
xanchor='left',
xref="paper",
yref="paper")
)
fig.update_traces(hovertemplate=None)
fig.update_layout(
plot_bgcolor='#060606',
paper_bgcolor='#060606',
font_color='white',
title=dict(
y = 0.82, #Adjust location of stock ticker title
x = 0.92,
xanchor = 'center',
yanchor = 'top',
font=dict(
family="Arial",
size=25,
color='white'
)
),
hovermode = 'x',
# xaxis_tickformat = '%b'
)
# Return below into children tag above
return [
html.H1(pathname,
style={'textAlign':'center'}),
dcc.Graph(id='stock-chart',
figure = fig
)
]
if __name__=='__main__':
app.run_server(debug=True, port=3000)
To get the desired behavior you can just hide the chart radio buttons for all endpoints that do not build the stock price graph. To do this add the style attribute of the period-selector element as an output to your main callback as shown below.
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
sidebar,
content,
callback_layout # Leave this here, do not move.
])
@app.callback(
Output("page-content", "children"),
Output("period-selector", "style"), # Add new output here
Input("url", "pathname"),
Input('period-selector', 'value')
)
def render_page_content(pathname, period_selector):
""" App routing from sidebar to content depending on path """
hidden_style = {'display': 'none'}
# It would be best to make visible_style a module level variable.
# Use it for both the app layout and this callback function.
# That way if you want to change its location or look you only have to edit one location.
visible_style = {'textAlign': 'center'}
if pathname == '/':
return homepage_layout, hidden_style
if pathname == '/test':
return 'Test', hidden_style
else:
# Your other build code here.
return [
html.H1(pathname,
style={'textAlign': 'center'}),
dcc.Graph(id='stock-chart',
figure=fig
)
], visible_style