How to update the figure of a px.line - python

I want to draw a chart of btc and show one hour after one hour like a slideshow (no animation or effect is needed). However I'm not able to update the figure stemming from px.line() without the browser opening a new tab. How would I do this, like remaining in the same tab and just updating the data of the line and redraw it?
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import time
df = pd.read_csv('btc.csv')
i=0
shown=False
part_df1=df.iloc[0:60]
fig = px.line(part_df1, x = 'date_x', y = 'price_y', title='btc price ..')
fig.show()
while i<600:
time.sleep(0.25)
i=i+60
part_df=df.iloc[i:i+60]
fig = px.line(part_df, x = 'date_x', y = 'price_y', title='btc price ..')
fig.show()
so something like:
fig.update(px.line(part_df, x = 'date_x', y = 'price_y', title='btc price ..'))
instead of
fig = px.line(part_df, x = 'date_x', y = 'price_y', title='btc price ..')
fig.show()

I've put together an example plotly-dash app that performs a live update for data over a predefined period of time. Some of the important features are:
the dcc.Interval object automatically triggers the callback function for updating the figure every interval of time (e.g. update the figure every 1 second). there's also an n_interval counter which will be useful for helping us keep track of the indices in the df we are iterating through (documentation here)
the extendData property of dcc.Graph allows you to return a dictionary from your callback (and saves you the trouble of having to directly modify the data inside the figure object). (documentation here)
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
# df = pd.read_csv('btc.csv')
## create some random walk data
np.random.seed(42)
change = np.random.choice([-1,0,1], 600-1)
btc = [20000]
for y_change in change:
btc_new = btc[-1] + 1000*y_change
btc.append(btc_new)
df = pd.DataFrame({
'date': pd.date_range(start='2022-01-01', periods=600),
'btc': btc
})
part_df=df.iloc[0:60]
fig = px.line(part_df, x = 'date', y = 'btc', title='btc price')
app = dash.Dash(__name__)
app.layout = html.Div(
html.Div([
dcc.Graph(id='live-update-graph', figure=fig),
dcc.Interval(
id='interval-component',
interval=1000, # 0.25*1000, # in milliseconds
n_intervals=1,
max_intervals=len(df) // 60 - 1
)
])
)
#app.callback(Output('live-update-graph', 'extendData'),
[Input('interval-component', 'n_intervals'),
Input('live-update-graph', 'figure')])
def extend_trace(n, fig):
if 60*n > len(df):
return {}
else:
part_df = df.iloc[60*n:60*(n+1)]
return (dict(
x=[part_df['date'].tolist()],
y=[part_df['btc'].tolist()],
))
app.run(debug=True)
Update: if you don't need to the data to extend, and only want to show the new incoming data, then you can use the figure property of dcc.Graph instead of extendData. Then your callback would look like the following:
#app.callback(Output('live-update-graph', 'figure'),
[Input('interval-component', 'n_intervals'),
Input('live-update-graph', 'figure')])
def extend_trace(n, fig):
if 60*n > len(df):
return fig
else:
part_df = df.iloc[60*n:60*(n+1)]
return (dict(
data=[dict(
x=part_df['date'].tolist(),
y=part_df['btc'].tolist(),
)]
))

Related

Add line plot to existing plotly express chart

import dash
from dash import Dash, html, dcc, Output, Input, callback
import plotly.graph_objects as go
import plotly.express as px
df1 = pd.read_csv(filepath+filename, index_col="Date")
df1.index = pd.to_datetime(df1.index)
df1["Measure1_SMA"] = df1["Measure1"].rolling(20).mean()
df1["Measure2_SMA"] = df1["Measure2"].rolling(20).mean()
app = Dash(__name__)
my_dropdown = dcc.Dropdown(options = ['Measure1', 'Measure2'],
value = df1.columns[:2],
multi = False,
style = {'width':'50%'})
my_graph = dcc.Graph(figure={})
app.layout = html.Div([
html.H1('Metrics (Values)', style = {'textAlign':'center'}),
html.Label("Metrics: "),
my_dropdown,
my_graph
])
#callback(
Output(component_id=my_graph, component_property='figure'),
Input(component_id=my_dropdown, component_property='value')
)
def update_graph(dropdown_value):
plot_figure = px.bar(data_frame=df1, y=dropdown_value, x=df1.index)
#plot_figure.add_line()
print(dropdown_value)
return plot_figure
if __name__ == "__main__":
app.run_server(debug=True)
I want to create a single plot on the plotly dashboard with an option to toggle between Measure1 and Measure2. Selecting the dropdown_value will create a bar graph of Measure1 on y-axis and Date on x-axis. I also want to plot a line graph on the same plot which will be the rolling average of previous 20 days for the value selected from the dropdown.
I tried adding a add_line() method but not sure how to use it.
Creates a data frame from which the value columns and SMA columns are extracted, using the values obtained from the drop-down selections. Draw a bar graph in the created data frame and add the SMA in scatter plot line mode. drawing two graphs, I think I need to make a graph with two axes. since I could not add graphs to px.line, I reused the data in px.line to create the first I have used the data from px.line as the first graph. The sample data is stock price data.
import dash
from dash import Dash, html, dcc, Output, Input, callback
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import yfinance as yf
df1 = yf.download("AAPL", start="2021-01-01", end="2021-03-01")
df1["Close_SMA"] = df1["Close"].rolling(20).mean()
df1["High_SMA"] = df1["High"].rolling(20).mean()
df1 = df1[['High','Close','Close_SMA','High_SMA']]
app = Dash(__name__)
my_dropdown = dcc.Dropdown(options = ['Close', 'High'],
value = 'Close',
multi = False,
style = {'width':'50%'})
my_graph = dcc.Graph(figure={})
app.layout = html.Div([
html.H1('Metrics (Values)', style = {'textAlign':'center'}),
html.Label("Metrics: "),
my_dropdown,
my_graph
])
#callback(
Output(component_id=my_graph, component_property='figure'),
Input(component_id=my_dropdown, component_property='value')
)
def update_graph(dropdown_value):
sma = '{}_SMA'.format(dropdown_value)
dff = df1[[dropdown_value,sma]]
dff = dff.dropna()
plot_figure = make_subplots(specs=[[{"secondary_y": True}]])
fig = px.bar(data_frame=dff, y=dropdown_value, x=dff.index)
plot_figure.add_trace(fig.data[0], secondary_y=False)
plot_figure.add_trace(go.Scatter(x=dff.index, y=dff[sma], name=sma, mode='lines'), secondary_y=True)
plot_figure.update_layout(yaxis_title='Close')
return plot_figure
if __name__ == "__main__":
app.run_server(debug=True)

Produce multiple plots using dash callbacks

I'm not clear how I could dynamically create multiple charts at once - or if that is not possible then how I could loop through a list of values using a single callback.
For example in the code below list of continents is a a list of filter options. Is it possible to basically make it so when this page loads, I see 5 charts automatically?
Currently, what I'm doing is I have to type 5 #app.callback...make_scatter_plot(option=_dropdown_value) which ends up with a million lines of code in my file and makes it hard to read even though everything is doing the same thing.
What am I missing? Thanks
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import pandas as pd
import numpy as np
app = Dash(__name__)
df = px.data.gapminder()
list_of_continents = ["Asia", "Africa", "Europe", 'Oceania', 'Americas']
app.layout = html.Div([
html.H4('Restaurant tips by day of week'),
dcc.Dropdown(
id="dropdown",
options=list_of_continents,
multi=False
),
dcc.Graph(id="graph"),
#dcc.Graph(id ='graph2') ##????
])
#app.callback(
Output("graph", "figure"),
Input("dropdown", "value")
)
def make_scatter_plot( value =[i for i in list_of_continents], df = df):
"""
"""
data = df[df['continent'].isin([value])]
fig = px.scatter(data, x="lifeExp", y="gdpPercap",
size="pop")
return fig
if __name__ == '__main__':
app.run_server(debug=True)
although plotly express can help you set up a graph with just one line of code it’s not very handy when it comes to customizing each trace. So, for that, you’ve to switch to graphs_objects.
In the following lines of code, the callback generates a Graph component for each trace and appends each graph component to a Div component. Hence you get multiple graphs using a single callback.
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
app = Dash(__name__)
df = px.data.gapminder()
app.layout = html.Div([
html.H4('Restaurant tips by day of week'),
html.Div(id='graphs',children=[])
])
#app.callback(
Output("graphs", "children"),
Input("graphs", "children")
)
def make_scatter_plot(child):
"""
"""
for continent in df['continent'].unique():
df_filtered = df[df['continent'] == continent]
fig = go.Figure()
fig.add_trace(
go.Scatter(x = df_filtered['lifeExp'],
y = df_filtered['gdpPercap'],
mode = 'markers',
marker = dict(size = 10 + (df_filtered['pop'] - df_filtered['pop'].min()) * 20
/ (df_filtered['pop'].max() - df_filtered['pop'].min())) # This is just to scale the marker size value between 10 and 20.
)
)
fig.update_layout(
title_text = continent
)
child.append(dcc.Graph(figure=fig))
return child
if __name__ == '__main__':
app.run_server(debug=True)
The output of the Code:
Click here

Rebase data to relative performance with rangeslider in Python Dash Graph update_layout

I am trying to use and adjust the rangeslider property in the update_layout of a Python Dash core component Graph. I like to rebase data to a relative performance chart whenever the rangeslider is changed such that the performance is always measured to the first element in the selected range. I was able to accomplish this by using a RangeSlider as an input for the Graph component. However, I was wondering if this is also possible only using the rangeslider property in the Graph component directly without the callback.
I am looking for something similar to this solution in R: https://mgei.github.io/post/rangeslider-plotly/.
# Libraries
import pandas as pd
import numpy as np
import datetime
from dash import Dash, html
import dash_core_components as dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import plotly.express as px
# Data
T = 100
steps = 10
base = datetime.datetime.today()
date_list = reversed([base - datetime.timedelta(days=x) for x in range(T)])
test_data = pd.DataFrame(np.random.randn(T)/100, index=date_list, columns=['Col1'])
test_data.iloc[0,:] = 0
# App
app = Dash(__name__)
app.layout = html.Div([html.H3('RangeSlider'),
dcc.RangeSlider(0, T, steps, count=1,
marks={i:test_data.index[i].strftime('%d.%m.%y') for i in range(0,T,steps)},
id='range_slider'),
html.Br(),
html.H3('Plot'),
dcc.Graph(figure={'data':[]}, id='plot_data'),
],
style={'width': '50%', 'display': 'inline-block', 'padding-left':'25%', 'padding-right':'25%'}
)
# Callbacks
#app.callback(Output('plot_data', 'figure'),
Input('range_slider', 'value'))
def plot_data(value):
if value is None:
raise PreventUpdate
else:
tmp_data = (1+test_data.iloc[value[0]:value[1],:]).cumprod() * 100
tmp_data.iloc[0,:] = 100
tmp_data = tmp_data.sort_index()
fig = px.line(tmp_data, y=['Col1'])
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True), type='date'))
fig.update_layout(showlegend=True)
return fig
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
The reason why it did not work is probably due to the fact that the time series data of the test data does not match the time series data of the slider.
My fixes included changing the date in datetime.date.today() to a date, changing the date format of the slider to a hyphenated format (unrelated to the failure), expanding the slider to 75% of the browser width because it was too short, and adjusting the margins.
# Libraries
import pandas as pd
import numpy as np
import datetime
from dash import Dash, html, dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
from jupyter_dash import JupyterDash
import plotly.express as px
# Data
T = 100
steps = 10
base = datetime.date.today()
date_list = reversed([base - datetime.timedelta(days=x) for x in range(T)])
test_data = pd.DataFrame(np.random.randn(T)/100, index=date_list, columns=['Col1'])
test_data.iloc[0,:] = 0
#fig = px.line(test_data, y=['Col1'])
# App
#app = Dash(__name__)
app = JupyterDash(__name__)
app.layout = html.Div([html.H3('RangeSlider'),
dcc.RangeSlider(0, T, steps, count=1,
marks={i:test_data.index[i].strftime('%d-%m-%y') for i in range(0,T,steps)},
id='range_slider'),
html.Br(),
html.H3('Plot'),
dcc.Graph(figure={'data':[]}, id='plot_data'),
],
style={'width': '75%', 'display': 'inline-block', 'padding-left':'10%', 'padding-right':'15%'}
)
# Callbacks
#app.callback(Output('plot_data', 'figure'),
Input('range_slider', 'value'))
def plot_data(value):
if value is None:
raise PreventUpdate
else:
tmp_data = (1+test_data.iloc[value[0]:value[1],:]).cumprod() * 100
tmp_data.iloc[0,:] = 100
tmp_data = tmp_data.sort_index()
print(tmp_data)
fig = px.line(tmp_data, y=['Col1'])
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True), type='date'))
fig.update_layout(showlegend=True)
return fig
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False, mode='inline')

Callback error on plotly dash dashboard when trying to add a dropdown to show pie charts

I'm quite new to dash but I'm trying to put together a data dashboard. Of the things I want to have is a drop down, that based on the input, renders 1 of two pie charts. The logic to structure the pie chart is included in my callback function. It is saying it is expecting 1 output but it had two. I've had a look online and tried different suggestions. I think I'm pretty close to getting this to work, there is just something dumb I'm not doing.
I know people here are wizards, so I was hoping someone might be able to help me. Also if anyone is Dash savvy, can you point me in the direction of good documentation to learn how to orient this, so I can change the layout to make these plots fit better together in a dashboard, rather than just a web page?
So much love
Thanks
import pandas as pd
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
from jupyter_dash import JupyterDash
import plotly.graph_objects as go
import plotly.express as px
from dash import no_update
import plotly.figure_factory as ff
app = dash.Dash(__name__)
df = pd.read_csv('nyc-jobs.csv')
#top job categories
counts = df['Job Category'].value_counts()
counts = pd.DataFrame(counts)
counts = counts.head(10)
counts.sort_values(['Job Category'],ascending=True, inplace = True)
fig = px.bar(df, y=counts.index, x=counts['Job Category'])
#Salary range distribution
salary_counts = df['Salary Range To'].value_counts()
salary_counts = pd.DataFrame(counts)
group_labels = ['Salary Range From','Salary Range To']
fig3 = ff.create_distplot([df['Salary Range From'],df['Salary Range To']], group_labels, bin_size= 10000)
fig4 = go.Figure()
fig4.add_trace(go.Box(y=df['Salary Range From'], name='Salary Range From',
marker_color = 'indianred'))
fig4.add_trace(go.Box(y=df['Salary Range To'], name = 'Salary Range To',
marker_color = 'lightseagreen'))
# # of positions
df.sort_values(by = ['# Of Positions'], ascending = True, inplace = True)
df_group = df.groupby(['Business Title']).mean(['# Of Positions'])
df_group.sort_values('# Of Positions', ascending = True, inplace = True)
df_group.index = df_group.index.str.capitalize()
fig5 = px.bar(df, y=df_group.index[-5:], x=df_group['# Of Positions'][-5:])
app.layout = html.Div([
html.H1("New York City Job Postings", style = {'text-align': 'center', 'font-family': 'Helvetica'}),
#Job postings graph
dcc.Graph(
id='Top Job Postings',
figure=fig
),
html.Div([html.H2('Report Type:', style={'margin-right': '2em', 'font-family': 'Helvetica'}),]),
dcc.Dropdown(id='input-type',
options=[
{'label': 'Full vs part time report ', 'value': 'OPT1'},
{'label': 'Posting type', 'value': 'OPT2'}
],
placeholder='Select a report type',
multi=False,
clearable=False,
style={'width':800, 'padding':3, 'font-size':20, 'text-align-last':'center', 'font-family': 'Helvetica'}),
html.Div(id='output_container', children=[]),
html.Div(dcc.Graph(id='pie_chart_reports')),
#Salary Distributions
dcc.Graph(
id="Salary Distribution",
figure = fig3),
dcc.Graph(
id="Salary Distribution boxplot",
figure = fig4),
dcc.Graph(
id='Highest number of positions',
figure=fig5
)
])
#app.callback(
[Output(component_id='pie_chart_reports', component_property='figure')],
[Input(component_id='input-type', component_property='value')]
)
def update_graph(report_type):
dff = df
container = "The chosen report was: {}".format(report_type)
if report_type == 'OPT1':
#full time vs part time
ft_pt = dff['Full-Time/Part-Time indicator']
ft_pt.fillna('Not listed', inplace = True)
ft_pt.replace('F', 'Full Time', inplace = True)
ft_pt.replace('P', 'Part Time', inplace = True)
value_counts_ft_pt = dff['Full-Time/Part-Time indicator'].value_counts()
labels_ft_pt = value_counts_ft_pt.index.tolist()
fig1 = px.pie(dff,
values = value_counts_ft_pt,
names = labels_ft_pt)
return container, dcc.Graph(id='pie_chart_reports',figure=fig1)
else:
#internal vs externl
value_counts_posting_type = dff['Posting Type'].value_counts()
labels_posting_type = value_counts_posting_type.index.tolist()
fig2 = px.pie(
df,
values = value_counts_posting_type,
names = labels_posting_type,
color_discrete_sequence=px.colors.sequential.Bluyl)
return container, dcc.Graph(id='pie_chart_reports',figure=fig2)
if __name__ == '__main__':
app.run_server(debug=True)
The first problem is that your callback has one output, but you return a tuple of two things. So you could add an Output that targets the element which you want to have the value of content, I'm guessing that element is the element with id output_container. The other option is to remove content from the return statement.
The second problem is that you have the Output surrounded by a list, so dash expects the return value to be a list containing one value. You can remove the list surrounding your Ouput so it expects a tuple
Output(component_id='pie_chart_reports', component_property='figure')
or you can surround your return values with a list.
The third problem is that you target the component_property figure, but you're returning a Graph component. So you should return fig1 instead of dcc.Graph(id='pie_chart_reports', figure=fig1) for example.

Windrose (Barpolar) in Dash is not updating

I want to implement a windrose (with Dash) for displaying the direction of wind data. I am using a callback function and the idea is that the data is displayed for different times - for this I am using a slider, where the time can be selected. The problem here is that after selecting the time, I have to double click on the windrose so that it is updated and displays the data.
I used the same code with a normal line plot, and there it worked fine (meaning that it updated right away without double clicking).
Thanks in advance!
# visit http://127.0.0.1:8050/ in your web browser.
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import pandas as pd
from dash.dependencies import Input, Output
import logging
# Load data
df = pd.read_csv('..\\..\\data\\raw\\fake_wind.csv', #
index_col=0,
parse_dates=True) # finds dates "automatically"
df.index = pd.to_datetime(df['Date']) # convert argument to datetime
# Initialize the app
app = dash.Dash(__name__)
# app.config.suppress_callback_exceptions = True # ??
app.layout = html.Div(
children=[
html.H1("Wind Direction"),
dcc.Slider(id='windrose',
min=1,#min(df.index),
max=5,#max(df.index),
value=5,
marks={
0: '0 °F',
3: '3 °F',
5: '5 °F',
7.65: '7.65 °F',
10: '10 °F'
}
),
html.Div(id='output_container', children=[]),
#html.Br(),
#dcc.Graph(id='sun_map', figure={})
dcc.Graph(id='wind_map2', config={'displayModeBar': False}, animate=True)
])
import numpy as np
#app.callback(
dash.dependencies.Output(component_id='wind_map2', component_property='figure'),
[dash.dependencies.Input('windrose', 'value')])
def update_output(value):
#fig=make_subplots(rows=2, cols=2, specs=[[{'type': 'polar'}]*2]*2)
row = df.iloc[value,:]
barplot = go.Barpolar(
r = [np.random.randint(1,10)],
width=[10],
theta = [np.random.randint(1,360)],
marker_color=["#E4FF87", '#709BFF', '#709BFF', '#FFAA70', '#FFAA70', '#FFDF70', '#B6FFB4'],
marker_line_color="black",
marker_line_width=2,
opacity=0.8
)
fig = go.Figure(data=barplot)
return fig
#return 'You have selected "{}"'.format(value)
fig.update_layout(
xaxis=dict(title="time"),
template='plotly_dark',
paper_bgcolor='rgba(0, 0, 0, 0)',
plot_bgcolor='rgba(0, 0, 0, 0)',
#yaxis="W/s^2",
#yaxis2=dict(title="Celsius",
#overlaying='y',
#side='right'),
font=dict(
family="Courier New, monospace",
size=18,
color="RebeccaPurple"
)
)
return fig
# Run the app
if __name__ == '__main__':
app.run_server(debug=True) # "hot-reloading" (Dash automatically refreshes browser when changes)
might be a bit late to answer. However I think you should change the "updatemode " in your slider from 'mouseup' to drag. As per the docs here https://dash.plotly.com/dash-core-components/slider
updatemode (a value equal to: 'mouseup', 'drag'; default 'mouseup'):
Determines when the component should update its value property. If
mouseup (the default) then the slider will only trigger its value when
the user has finished dragging the slider. If drag, then the slider
will update its value continuously as it is being dragged. If you want
different actions during and after drag, leave updatemode as mouseup
and use drag_value for the continuously updating value.

Categories

Resources