Updating video source on graph click in Dash Python - python

I have a html.video element in my app layout, along with a graph, when I click on an element of the graph I want to update the video element, to a video corresponding that section of the dataset.
The below is how I have my app layout set up.
app.layout = html.Div([
html.H1("Belfast Harbor Data Overview", style ={'text-align': 'center'}),
#Droptown for machine type
dcc.Dropdown(id="slct_machine",
options=[{'label': k, 'value': k} for k in all_options.keys()],
multi=False,
value=options1[0],
style={'width': "70%"}
),
#Dropdown for date
dcc.Dropdown(id="selct_date",
style={'width': "70%"},
),
#HTML Break
html.Br(),
dcc.Graph(id='hist', figure={}, style={'width': "90%"},
config={
'staticPlot': False, # True, False
'scrollZoom': True, # True, False
'doubleClick': 'reset', # 'reset', 'autosize' or 'reset+autosize', False
'showTips': False, # True, False
'displayModeBar': True, # True, False, 'hover'
'watermark': True,
# 'modeBarButtonsToRemove': ['pan2d','select2d'],
},),
html.Video(
controls=True,
id='video_player',
src={},
autoPlay=False,
style={'width': "70%"}
),
After this, I have some callback functions for my graph and a function to populate the graph with data.
Following this, I have another app call back and here I am trying to update the video player with some test data. I have done this as follows:
#app.callback (
Output('video_player', 'src'),
[Input('hist', 'clickData')]
)
def updateVideo(clk_data, src):
if clk_data is None:
video_src = "https://www.w3schools.com/html/mov_bbb.mp4"
else:
print(f'hover data: {clk_data}')
video_src = "http://techslides.com/demos/sample-videos/small.webm"
return video_src
I get the following callback error when I do this:
I am not really sure why the src is not updating, as passing components seems to work like this for figures and containers.
There are other elements to the app as well but I feel they aren't consequential as the video source is the part that is not updating.
I can provide all the code if required, but I feel I am simply updating the video source incorrectly.

The number of arguments in your function definition (currently two) must match the number of inputs (+ number of states) to your callback (currently one). Hence your should remove the (unused) src argument, or add another input or state.

Related

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

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')])

Live Updating Component Callback Dash

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.

Unselect active_cell in dash datatable (python)

All,
I am trying to implement a dash datatable, where I select rows by a direct click on it (no radio buttons). Currently, I am doing this with the active_cell and it works well:
No matter in which cell of a row a user clicks, a graph is updated with the data in that row. If he clicks another cell in the same row, the data is unselected (via a dcc.storage)
Here comes my problem: If the user clicks the same cell again, there is no active_cell event triggered. Therefore, my callback is not triggered and nothing happens.
I would like to deselect that cell the second time the user clicks it. How can I implement that?
Thanks!
So... I solved this... it is not pretty but it works - it includes a loopbreaker which I had to implement to avoid a circular dependency, but yeah - I am absolutely open for cleaner solutions.
Find below the callbacks
# takes user selected cell (active_cell) and the current state of a dcc.storage (tableclick) which stores the last saved row that was clicked
# output: updated selected_rows for the datatable, styling for the selected row and update for dcc.storage
# if no cell is selected, do nothing
# if no cell is selected, but there is a row stored as selected, highlight that row (this is a consequence from the circular dependency)
# if a cell is selected that is different from the previous cell, highlight that new row. Otherwise, deselect the row.
#app.callback(
[Output('performancedatatable', 'style_data_conditional'), Output('tableclick', 'data'),
Output('performancedatatable', 'selected_rows')],
[
Input('performancedatatable', 'active_cell'),
], [State('tableclick', 'data')]
)
def highlight_row(cell, prevrow):
if cell is None:
if prevrow is None:
return [{}], None, []
else:
return [{}], None, prevrow
elif "row" in cell:
if cell.get("row", "") == prevrow:
return [{}], None, []
else:
return ([{
"if": {"row_index": cell.get("row", "")},
"backgroundColor": "rgba(146, 192, 234, 0.5)",
}], cell.get("row", ""), [cell.get("row", "")])
# Is triggered by changing the dcc.storage "tableclick"
# resets active cell and selected cell via callback below
#app.callback([Output('loopbreaker_div', "children")], [Input('tableclick', 'data')])
def reset_active_cell(input):
return [html.Div(id='loopbreaker', children=True)]
#loopbreaker to avoid circular dependency
#app.callback([Output('performancedatatable', "active_cell"), Output('performancedatatable', 'selected_cells')], [Input('loopbreaker', 'children')])
def reset_active_cell(input):
time.sleep(1)
return (None, [])
Shoutout to http://yaaics.blogspot.com/2019/03/circular-references-in-plotlydash.html for helping resolving the circular dependency
A simpler solution (but it has other drawbacks) is to select a hidden cell. This makes it appear to the user that nothing is selected.
In the example below a cell is processed and de-selected by a callback. Clearly this callback could also be for a "deselect all" button or whatever else you need.
Add column called "cant_see":
df = pd.read_csv("my_data.csv")
df.insert(0, "cant_see", ["" for i in df.iloc[:, 0] ])
Make it hidden when you create the DataTable using style_data_conditional and style_header_conditional:
dash_table.DataTable(
id="table",
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict("records"),
is_focused=True,
style_data_conditional=[
{'if': {'column_id': 'cant_see',}, 'display': 'None',}
],
style_header_conditional=[
{'if': {'column_id': 'cant_see',}, 'display': 'None',}
],
)
and then a callback can set the table's "active_cell" and/or "selected_cells"
#app.callback(
Output("table", "active_cell"),
Output("table", "selected_cells"),
Input("table", "active_cell"),)
def cell_clicked(cell):
# Do something useful with cell,
# such as toggling it's style to mimic select/de-select
# Now make it look like the cell is de-selected
# by selecting a hidden cell
#
# return active cell 0,0 and selected_cells list [ 0,0 ]
return {'column':0, 'row':0}, [{'column':0, 'row':0}]

How to update chained dropdown value in Dash dcc?

In dash, how do I update the values of one dropdown into another checklist or slider?
In the below code, I am selecting one value from a dropdown which should update checklist values based on the selected value from the dropdown. Here I am partially successful in taking value from the dropdown but it's accumulating with older selected values in the checklist.
Please find below part of the code.
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
from dash.exceptions import PreventUpdate
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config['suppress_callback_exceptions'] = True
app.layout = html.Div([
dash_table.DataTable(
id='datatable-upload-container',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict('records'),
),
html.Div(dcc.Dropdown(
id='data_selector1',
options=[
{'label': '', 'value': ''}
],
value=[]
)
),
html.Br(),
html.Div([
html.Div(id='numerical-slider'),
# this is a hack: include a hidden dcc component so that
# dash registers and serve's this component's JS and CSS
# libraries
dcc.Input(style={'display': 'none'})
])
])
#app.callback(Output('data_selector1', 'options'),
[Input('datatable-upload-container', 'data')])
def update_dropdown(rows):
print('updating menus')
numerical_col = [i for i in df.columns if df[i].dtypes != "object"]
col_labels=[{'label' :k, 'value' :k} for k in numerical_col]
return col_labels
#app.callback(Output('numerical-slider','children'),
[Input('data_selector1', 'value'),
Input('datatable-upload-container', 'data')])
def explanatory_cat_slider(value, rows):
if value:
categories, intervals = pd.cut(df[value], 3, retbins=True)
return html.Div(html.Label([value,
dcc.RangeSlider(id='numerical-slider',
min=min(intervals),
max=max(intervals),
step=None,
marks={str(val): str(round(val,2)) for val in intervals},
value = [intervals[0],intervals[-1]]
)
],style={'width': '50%', 'display': 'inline-block', 'textAlign': 'left'})
)
else:
return []
if __name__ == '__main__':
app.run_server(debug=False)
updated code...
I am getting an issue with explanatory_cat_slider, it's not getting updated with new selected values.
In the first image I can select one value of dropdown which automatically shows slider of that value
In the second image sliders getting accumulated on upon other. How do I rectify this issue?
In the last image, how it becomes slider overlapped
The code you posted is incomplete, because you have the Input ID data_selector and the ID datatable-upload-container in your callback, but they do not exist in your layout. I can see that the callback will have a problem with its Output as well, because it's trying to update the children of categorical-checklist with an element that contains an ID of the same. This would result in non-unique IDs in your layout, which will break.
I may be able to help more with the complete code but, essentially, you have the right idea to check if value: and run your code inside that block. Make sure you do not let the callback silently end, because that will return None and cause trouble for you. You should include a return [] or return '' at the end, or inside an else: block to protect against that, and keep the children component valid, even if only with an empty value.
You should also include the dcc.Checklist in your initial layout. Give it options=[] to start with and have your callback update its options prop. That should work but, if there are still problems, update your posted code and I'll take another look.

Categories

Resources