Live Updating Component Callback Dash - python

I am having trouble wrapping my head around Dash component callbacks. I am simply trying to update on an interval. The backend that I am pointing to is a database that will update. Below is what I am working with. The function produces the figure I want well, but when the datasource inevitably updates I would like the figure to reflect that.
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(
children=[
html.Div(id="Graphs", children=[dcc.Graph(id='lapTime')]),
dcc.Interval(id='interval-component',
interval=1 * 1000,
n_intervals=0)
]
)
#app.callback(Output('Graphs', 'children'),
Input('interval-component', 'n_intervals'))
def lapTimePlot():
# SQL Calls for data
channel = pyodbc.connect("DRIVER={ODBC Driver 17 for SQL Server};~~connection String Omitted~~")
Run=pd.read_sql("SQL OMITTED", channel).iloc[0]['Name']
Event=pd.read_sql("SQL OMITTED", channel).iloc[0]['evID']
Lap=pd.read_sql("SQL OMITTED", channel)
#Clean Data
Lap=Lap[Lap['RunName']==Run]
Lap=Lap[Lap['evID']==Event]
Lap=Lap[Lap['Ltime']>10]
Lap=Lap[Lap['Ltime']<(Lap['Ltime'].min()*1.1)]
# Generate tables and sort values
med=Lap.groupby(['Driver'])['Ltime'].median().sort_values()
mini=Lap.groupby(['Driver'])['Ltime'].min().sort_values()
# Combine tables and put into dataframe
tab=pd.concat([mini,med],axis=1)
tab=tab.reset_index()
tab.columns = ['Driver','Min_Ltime','Mean_Ltime']
tab=tab.sort_values('Min_Ltime',ascending=True)
tab=tab.reset_index(drop=True)
tab=tab.reset_index()
tab.columns = ['Rank','Driver','Min_Ltime','Mean_Ltime']
tab.Rank=tab.Rank+1
tab=tab.round({'Min_Ltime': 3,'Mean_Ltime': 3})
# Set order by laptime
Lap['Driver_Sort']= pd.Categorical(Lap['Driver'], categories=tab.Driver, ordered=True)
Lap=Lap.sort_values('Driver_Sort',ascending=True)
ses="Session: " + Run
rheight=650/tab.Driver.count()
#Generate Table
trace=go.Table(
domain=dict(x=[0,0.2],
y=[0,1]),
columnwidth=[1, 3, 1, 1],
header=dict(
values=["Rank","Driver", "Fast Lap", "Mean Lap",],
height=50,
font=dict(size=10),
align="center"),
cells=dict(
values=tab.transpose().values.tolist(),
height=rheight))
# Generate box plot
trace1=go.Box(
x=Lap['Ltime'], y=Lap['Driver'] ,
notched=True,
orientation='h',
xaxis="x1",
yaxis="y1")
layout = dict(xaxis1=dict( dict(domain=[0.28, 1], anchor='y1')),
yaxis1=dict( dict(domain=[0.05, 0.93], anchor='x1')),
title_text=ses)
Fig1 = go.Figure(data = [trace,trace1], layout = layout)
return Fig1
if __name__ == "__main__":
app.run_server(debug=False)
I am struggling to see where this is going pear shaped. First time working with Dash and callbacks.
The page opens, and is updating every 1 second like it is supposed to, but the graph has nothing on it and is seemingly not being rendered by the lapTimePlot()
UPDATE:
When running with app.run_server(debug=True,use_reloader=False) there are no errors that can be seen when debugging.

The first problem I can identify is that you're missing a parameter for your callback function.
Each callback function requires at least one Input and Output. The inputs map to the parameters of the callback function by their position.
#app.callback(Output("Graphs", "children"), Input("interval-component", "n_intervals"))
def lapTimePlot(n_intervals):
# Do something...
You need to supply this parameter here even if you don't use it in the callback. You can name it something else as long as you supply a number of parameters to lapTimePlot equal to the number of Inputs used.

Related

Dash app update of current value without function

i have found a nice code on stackoverflow
( Dash app, plotly chart data outside the chart area)
I have change to "pie-figure"
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
#from datetime import datetime
import plotly.graph_objs as go
import random
labels = ['Mom','Max']
y_values = [] # finally: 1st value read c:\\test.txt ( only 1 value is inside,
# sec value = 6000 - 1st value )
# Initialize the dash app
app = dash.Dash()
app.layout = html.Div([
dcc.Graph(id='live-graph', animate=True,responsive=True),
dcc.Interval(
id='graph-update',
interval=1000,
n_intervals=0
),
])
# Define the callback function
#app.callback(Output('live-graph', 'figure'), [Input('graph-update', 'n_intervals')])
def update_graph(n):
current_value= random.randint(2000, 8000)
# Get the response value and append it to the y_values list
y = current_value
y_values.append(y)
# Create the line chart
trace = go.Pie(labels=labels, values=y_values)
data = [trace]
layout = go.Layout(title='Real-time Data')
return go.Figure(data=data)
if __name__ == '__main__':
app.run_server(host="192.168.178.26", port=8050, debug=True)
why does the current value didnĀ“t change?
what must i do?
Many thanks!
I think there's two main problems you need to fix:
the way you have written your dash app, the labels will stay the same after every update, but the labels need to be the same length as the y_values (see the documentation for go.Pie). To fix this, I randomly selected a new label from ["Mom", "Max"] along with a new y-value for every update (but this can be changed if you intended for the dash app to do something different)
I believe the argument animate=True for dcc.Graph only applies to certain types of charts like bar charts and line charts where you can draw something smooth. for a pie chart, you don't need an animation (you can just redraw the chart) and I think this is what is causing the code to break. once I set animate=False, the pie chart updates
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
#from datetime import datetime
import plotly.graph_objs as go
import random
labels = []
y_values = [] # finally: 1st value read c:\\test.txt ( only 1 value is inside,
# sec value = 6000 - 1st value )
random.seed(42)
# Initialize the dash app
app = dash.Dash()
app.layout = html.Div([
dcc.Graph(id='live-graph', animate=False, responsive=True),
dcc.Interval(
id='graph-update',
interval=1000,
n_intervals=0
),
])
# Define the callback function
#app.callback(Output('live-graph', 'figure'), [Input('graph-update', 'n_intervals')])
def update_graph(n):
current_value= random.randint(2000, 8000)
current_label= random.choice(['Mom','Max'])
# Get the response value and append it to the y_values list
y = current_value
y_values.append(y)
labels.append(current_label)
# Create the pie chart
trace = go.Pie(labels=labels, values=y_values)
# trace = go.Bar(x=labels, y=y_values)
data = [trace]
layout = go.Layout(title='Real-time Data')
return go.Figure(data=data, layout=layout)
if __name__ == '__main__':
app.run_server(port=8050, debug=True)

Is there a way to combine a loading data callback and a recurrent updating data callback with dash-python?

I am working on a dashboard with dash-plotly and I have created two different callbacks to handle data storage. The first callback loads initial data based on a dropdown
#app.callback(Output('battery-data','data'),
[Input('battery-dropdown', 'value')],
[State('battery-data','data')]
)
def load_data(battery_name):
example_data = utils_new.load_dump_from_mongo(collection,battery_name)
example_pd = pd.DataFrame(example_data)
print(example_pd.head())
example_pd = example_pd.drop('_id',axis=1)
return(example_pd.to_json(date_format='iso', orient='split'))
While the second gets as input the initial data and then updates it recurrently
#app.callback(Output('battery-data', 'data'),
[Input('interval-component', 'n_intervals'),
Input('battery-data','data'),
Input('battery-dropdown', 'value']
) [State('battery-data', 'data')])
def update_data(n, battery_json, battery_name, time_range):
example_old_data = pd.read_json(battery_json, orient='split')
example_old_data['Time'] = pd.to_datetime(example_old_data['Time']).apply(lambda x: x.replace(tzinfo=None))
last_record_time = parser.parse(str(example_old_data['Time'].tail(1).item()))
# load last 10 seconds of data as a list of records
update_data = utils_new.update_data_from_mongo(collection,battery_name,last_record_time)
update_pd = pd.DataFrame(update_data)
print('SIZE OF THE UPDATE', len(update_pd))
update_pd.drop("_id", axis=1, inplace=True)
# add update data
len_update = len(update_pd)
example_new_data = example_old_data.append(update_pd,ignore_index=True)
example_new_data.drop(example_new_data.head(len_update).index,inplace=True)
example_new_data = example_new_data.reset_index(drop=True)
# filter for battery id
example_new_data_battery = example_new_data[example_new_data['Battery_ID'] == battery_name]
return(example_new_data.to_json(date_format='iso', orient='split'))
app.layout = dbc.Container([
dbc.Row([
html.H1(children='Battery monitoring'),
html.Div(children='''
This dashboard shows battery state
'''),
html.Br(style={'marginBottom': '10px'})
]),
dbc.Row([
dbc.Col([
dbc.Row([batteries_drop()]),
],width=6),
dbc.Col([
dbc.Row(id='card-anomaly'),
], width=6)]),
dcc.Store(id='battery-data')
dcc.Interval(id='interval-component',
interval=10*1000,
n_intervals=0)
])
However, in this way, it doesn't work because I use the same ID for two different callbacks. Do you know if there is a way to make it work? Or if you know a strategy to address this? Initially, I would like to load only the data from a single battery to avoid crashing the dashboard and then I need to get the data updated every 10 secs.
Thanks for any suggestion or help!
You can fix this issue by using the great dash-extensions library.
This library has some modules that modify the Dash and allow us to extend it to do more than the standard Plotly does. By using a module called MultiplexerTransform combined with DashProxy you will be able to use the same ID in different callback outputs;
Below is a code snippet which does exactly what you did, you just need to adjust it to your context.
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform, html
app = DashProxy(transforms=[MultiplexerTransform()])
app.layout = html.Div([
html.Button("Left", id="left"),
html.Button("Right", id="right"),
html.Div(id="log")
])
#app.callback(Output("log", "children"), Input("left", "n_clicks"))
def left(n_clicks):
if not n_clicks:
raise PreventUpdate()
return "left"
#app.callback(Output("log", "children"), Input("right", "n_clicks"))
def right(n_clicks):
if not n_clicks:
raise PreventUpdate()
return "right"
if __name__ == "__main__":
app.run_server()
To read more about this module and library you can read: dash-extensions
Regards,
Leonardo

Store edited DataTable in Dash

I am trying to store an edited Python Dash DataTable into a dcc.store container with a button component and use the saved data at a later point. However, once I change the original DataTable, the stored container also changes. I found the following post (save a modified dash datatable to dataframe) but was not able to figure it out.
My core question is how can I store (copy) some data in a Python dash app and then manipulate the original data without altering the stored copy.
# Libraries
import pandas as pd
import dash
from dash import html, dash_table
import dash_core_components as dcc
from dash.dependencies import Input, Output
# Data
test_data = pd.DataFrame([[1,2],[3,4]], columns=['Col1', 'Col2'])
saved_test_data = None
# App
app = dash.Dash(__name__)
app.layout = html.Div(children=[dash_table.DataTable(columns = [{"name": i, "id": i} for i in test_data.columns],
data = test_data.to_dict('records'),
id = 'test_data_table',
editable=True,
),
html.Button('Save data', id='save_test_data', n_clicks=0),
dcc.Store(id = 'test_data_store'),
dash_table.DataTable(id = 'check_table', data=saved_test_data),
],
style={'width': '50%', 'display': 'inline-block', 'padding-left':'25%', 'padding-right':'25%'}
)
# Callbacks
#app.callback(Output('test_data_store', 'data'),
[Input('save_test_data', 'n_clicks'), Input('test_data_table', 'data')])
def save_test_data(n, data):
if n == 0:
saved_test_data = None
else:
saved_test_data = data
return saved_test_data
#app.callback(Output('check_table', 'data'),
Input('test_data_store', 'data'))
def restore_saved_test_data(data):
return data
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
If I change a value in the upper table after pressing the button, the lower table also changes, but it should display the value saved before the click, i.e.
Original Table
Press the button to save the table.
Stored table should be displayed below
Changing the upper table should not change the lower table before hitting the button again, but it changes immediately.
There are two problems. First, you have the original table as an Input, which triggers the callback every time it changes. This should be a State instead,
State('test_data_table', 'data')
Second, and maybe not so important if you make the above change, is this condition, if n == 0:. This will always be False after one click of the button. From there, every time the table updates.

Hooking up dcc.Slider to a clientside_callback in Dash for animated graph

I want to have a basic Dash app with an animated graph that you can Play/Pause, and change the time it displays with a dcc.Slider. I also need to have the current frame that the animation is exposed available so that I can also display additional graphs/datatables linked to the data currently being displayed, so I can't just use a Plotly graph.
My data is large enough (think, ~3 columns, 50k rows, with ~60 points displayed at a time) that using server-side callbacks is very slow. So, instead I stream chunks of the data into a dcc.Store every n intervals, use another dcc.Store to keep track of the current animation frame, and then use a clientside callback to update the graph.
At the moment, I have the slider value set up to mirror the stored frame, so that it automatically updates. However, I'm having trouble figuring out a way to let the user move the value of the slider, and have the graph update accordingly. Since the dcc.Store with the frame is being updated in the clientside callback, it can't be updated elsewhere. This is a simple version of what the code looks like now:
# -*- coding: utf-8 -*-
# Run this app with `python app.py` and
# visit http://127.0.0.1:8050/ in your web browser.
import pandas as pd
import numpy as np
import dash
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table # DataTable library
import plotly.express as px
import plotly.graph_objects as go
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SLATE])
df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
columns=['x', 'y', 'time'])
# Initial trace/figure
trace = go.Scatter(x=df.x[:1], y=df.y[:1],
name='Location',
mode='markers'
)
fig = go.Figure(data=[trace])
app.layout = html.Div([
# Animated graph
dcc.Graph(
id="graph",
figure=fig.update_layout(
template='plotly_dark',
paper_bgcolor= 'rgba(0, 0, 0, 0)',
),
),
# Slider to control
dcc.Slider(
id="slider",
min=0,
max=len(df),
value=0
),
# Buttons
dbc.ButtonGroup([
# Play
dbc.Button(
"Play",
color='success',
id="dashPlay",
n_clicks=0
),
# Pause
dbc.Button(
"Pause",
color='danger',
id="dashPause",
n_clicks=0
),],
size="lg"),
# Datatable
dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns], # Don't display seconds
data=df.to_dict('records')
),
# Storing clientside data
dcc.Store(
id='store',
data=dict(x=df.x, y=df.y) # Store figure data
),
dcc.Store(
id='frame',
data=0 # Store the current frame
),
# Client-side animation interval
dcc.Interval(
id="animateInterval",
interval=2000,
n_intervals=0,
disabled=True
),
])
# Update the animation client-side
app.clientside_callback(
"""
function (n_intervals, data, frame) {
frame = frame % data.x.length;
const end = Math.min((frame + 1), data.x.length);
return [[{x: [data.x.slice(frame, end)], y: [data.y.slice(frame, end)]}, [0], 1], end, end]
}
""",
[Output('graph', 'extendData'), Output('frame', 'data'), Output('slider', 'value')],
[Input('animateInterval', 'n_intervals')], [State('store', 'data'), State('frame', 'data')]
)
# Handles updating the data tables
# --------------------------------
# Updates the currently displayed info to show the
# current second's data.
# https://community.plotly.com/t/update-a-dash-datatable-with-callbacks/21382/2
#app.callback(
Output("table", "data"),
Input("frame", "data"),
)
def updateTable(frame):
# Once the animation has started...
if frame:
return (df.loc[df['time'] == frame]).to_dict('records')
else:
return (df.loc[df['time'] == 0]).to_dict('records')
# Handles pause/play
# ------------------
# Starts/pauses the 'interval' component, which starts/pauses
# the animation.
#app.callback(
Output("animateInterval","disabled"),
Input("dashPlay", "n_clicks"),
Input("dashPause", "n_clicks"),
State("animateInterval","disabled"),
)
def pause_play(play_btn, pause_btn, disabled):
# Check which button was pressed
ctx = dash.callback_context
if not ctx.triggered:
return True
else:
button = ctx.triggered[0]['prop_id']
if 'dashPlay' in button:
return False
else:
return True
if __name__ == '__main__':
app.run_server(debug=True)
As you can likely see, as of now there isn't a way to change the value of the slider and have it change the frame of the animation.
My first thought was simply adding a context check in the clientside callback, and try and account for the slider changing there, but I couldn't figure out how to do that with clientside_callback.
The only thing I can think of is having a second dcc.Store with the current frame, so there's Store A and Store B. Then I'd have two callbacks, the clientside callback to update the animation, and another to read any changes in the slider values. Then, the structure would be:
Clientside Callback:
In: Store A
Out: Store B
Slider Callback:
In: Store B
Out: Store A
Then if the slider value changed (i.e., the user moved the slider), that would be reflected in Store A, and would update in the animation. Similarly, the slider would be updated by the clientside callback and move to an appropriate value. However, this seems to re-introduce waiting on the server into the animation for the graph, and it feels like this must be a solved problem with a better solution. I'd love any advice on the topic!
What you can do is catch any user intervention when they play with the slider. Then you set frame to the value retrieved from the slider.
In your case, this can be something like:
app.clientside_callback(
"""
function (n_intervals,slider_value, data, frame) {
const triggered = dash_clientside.callback_context.triggered.map(t => t.prop_id);
frame = frame % data.x.length;
if (triggered == "slider.value") {
frame = slider_value;
}
const end = Math.min((frame + 1), data.x.length);
return [[{x: [data.x.slice(frame, end)], y: [data.y.slice(frame, end)]}, [0], 1], end, end]
}
""",
[Output('graph', 'extendData'), Output('frame', 'data'), Output('slider', 'value')],
[Input('animateInterval', 'n_intervals'),Input('slider', 'value')], [State('store', 'data'), State('frame', 'data')])

How can I cache my SQL result so I don't have to call SQL repeatedly to get data for Dash plots?

I am trying to build a dashboard that will generate several plots based on a single SQL data query. I want the query to be modifiable via the dashboard (e.g. to query a different order amount or similar), and then change all plots at once. The query maybe expensive so I don't want it to run N times for N different plots.
I have tried to do this using the flask cache decorator #cache.memoize(), similar to the example given in the docs: https://dash.plotly.com/performance
Here is a stripped back version of what I'm doing. I can tell that the query_data function is not doing what I intend because:
1. the resulting graphs show different data points on the x-axis. If it was using the same cached dataset the data points in x should be the same
2. The print statements in the query_data function come out twice everytime I change an input cell.
Can anyone explain why this isn't working or how I can achieve what I want.
import sys
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
from setup_redshift import setup_connection
from flask_caching import Cache
from datetime import datetime
import pandas as pd
conn = setup_connection()
app = dash.Dash(__name__)
cache = Cache(app.server, config={
# 'CACHE_TYPE': 'filesystem',
'CACHE_TYPE': 'memcached',
'CACHE_DIR': 'cache-directory'
})
sql_query = '''select i.order_amount_in_usd, r.calibrated_score, r.score
from datalake.investigations i
inner join datalagoon.prod_model_decision r
ON i.investigation_id = r.investigation_id
where i.team_id = {}
AND i.order_amount_in_usd < {}
AND r.calibrated_score >= 0
order by RANDOM()
limit 1000'''
#cache.memoize()
def query_data(team_id, max_usd):
print("Calling data query now with team_id={} and max_usd={} at time {}".format(team_id, max_usd, datetime.now()))
_sql = sql_query.format(team_id, max_usd)
print(_sql)
data = pd.read_sql(sql_query.format(team_id, max_usd), conn)
print("data is {} rows ".format(len(data)))
print("data max usd is {}".format(data['order_amount_in_usd'].max()))
return data
#app.callback(Output(component_id='output-graph', component_property='figure'),
[Input(component_id='data-select-team-id', component_property='value'),
Input(component_id='data-select-max-usd', component_property='value')])
def plot_data(team_id, max_usd):
print("calling query_data at from graph at {}".format(datetime.now()))
in_data = query_data(team_id, max_usd)
print("going to make graph1 now at {}".format(datetime.now()))
fig = px.scatter(in_data,
x='order_amount_in_usd',
y='calibrated_score')
return fig
#app.callback(Output(component_id='output-graph2', component_property='figure'),
[Input(component_id='data-select-team-id', component_property='value'),
Input(component_id='data-select-max-usd', component_property='value')])
def plot_second_data(team_id, max_usd):
print("calling query_data at from graph2 at {}".format(datetime.now()))
in_data = query_data(team_id, max_usd)
print("going to make graph2 now at {}".format(datetime.now()))
fig = px.scatter(in_data,
x='order_amount_in_usd',
y='score')
return fig
app.layout = html.Div( # style={'backgroundColor': colors['background']},
children=[dcc.Input(id='data-select-team-id',
value=7625,
placeholder='Input Team ID',
type='number',
min=0,
max=1_000_000_000,
debounce=True
),
dcc.Input(id='data-select-max-usd',
value=5000,
type='number',
debounce=True),
dcc.Graph(id='output-graph'),
dcc.Graph(id='output-graph2')]
)
if __name__ == '__main__':
app.run_server(debug=True)
In the past Ive stored the results using dcc.Store (see here)
You could structure your app like this:
Run the SQL query and store the results using dcc.Store (local or
memory depending on your use case). This only runs once (per app load, interval timer or user button refresh etc)
Callbacks to generate different
cuts of the data in dash tables or charts would load the store
If the results of the query are large (see 'Storage Limitations; in the above link) then you should save the results to a local flat file such as JSON or CSV and read that each time.
An alternative is to use PostgreSQL and materialized views to make the SQL query cheap (with a trade off on storage space)
These approaches makes the dash app appear very responsive to the user while allowing the analysis of large data

Categories

Resources