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.
Related
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
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.
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')])
New to Dash so bear with.
I have a websocket connection that streams forex data to my console; price, high, low, symbol etc.
I'd like to have that information displayed on a dashboard that is easily viewable, instead of everything being printed to my console.
The issue i face is the data only updates on page refresh, when i'd like it to update as it is received, on the dashboard.
This is my code to connect to the websocket and create the dashboard:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly
from dash.dependencies import Input, Output, State
import fxcmpy
import pandas as pd
import datetime as dt
import time
con = fxcmpy.fxcmpy(access_token = "1111111111111111111111111111111111111111111111212", log_level = 'error')
currenc = ["AUD/CAD", "AUD/CHF", "AUD/JPY", "AUD/NZD", "AUD/USD", "CAD/CHF", "CAD/JPY", "CHF/JPY", "EUR/AUD", "EUR/CAD", "EUR/CHF", "EUR/GBP", "EUR/JPY", "EUR/NZD", "EUR/TRY", "EUR/USD", "GBP/AUD", "GBP/CAD", "GBP/CHF", "GBP/JPY", "GBP/NZD", "GBP/USD", "NZD/CAD", "NZD/CHF", "NZD/JPY", "NZD/USD", "USD/CAD", "USD/JPY"]
app = dash.Dash(__name__)
def print_data(data, dataframe):
t = pd.to_datetime(int(data['Updated']), unit='ms')
price = data['Rates'][0]
symbol = data['Symbol']
app.layout = html.Div([
dcc.Textarea(
id='textprice',
value=(str(price)),
style={'width': '100%', 'height': 300},
),
dcc.Textarea(
id='textsymbol',
value=(str(symbol)),
style={'width': '100%', 'height': 300},
),
html.Div(id='textarea-example-output', style={'whiteSpace': 'pre-line'})
])
if __name__ == '__main__':
for i in currenc:
con.subscribe_market_data(i, (print_data,))
app.run_server(debug=True)
The dashboard contains two text boxes, one for price, the other for symbol. I'd like to be able to have the dashboard open and those values update automatically.
Any help will be appreciated,
What you need is the Interval component. Set up the dcc.Interval for whatever time period you want, and use it as the input to a callback function. The output from that function will be the text boxes with the data you want to refresh each time the interval updates. Very basically:
app.layout = html.Div([
...
dcc.Interval(id='my-interval', interval=5000), # one tick each 5 seconds
html.Div(id='my-output', children=[]),
...
])
#app.callback(Output('my-output', 'children'),
[Input('my-interval', 'n_intervals')])
def callback_func(interval):
# make your calls to get the data
return [html.Div([
# the data content in here
])
I would like to get inputs x and y from a user's input to a text box.
if x + 2*y 3*x*y > 100:
print('Blurb 1')
else:
print('Blurb 2')
This seems to be convoluted in dash with callbacks, etc, even though it could be self-contained and quite simple. Is there a simple way to do this within a web app? Other resources I found seem to assume a much more complex goal, so I am curious as to how much the code can be pared.
I don't think you can accomplish your task without defining a callback, but the code to get the work done is very short and simple. A possible solution could be the following:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
app.layout = html.Div([
html.H1("Simple input example"),
dcc.Input(
id='input-x',
placeholder='Insert x value',
type='number',
value='',
),
dcc.Input(
id='input-y',
placeholder='Insert y value',
type='number',
value='',
),
html.Br(),
html.Br(),
html.Div(id='result')
])
#app.callback(
Output('result', 'children'),
[Input('input-x', 'value'),
Input('input-y', 'value')]
)
def update_result(x, y):
return "The sum is: {}".format(x + y)
if __name__ == '__main__':
app.run_server(host='0.0.0.0', debug=True, port=50800)
This is what you get:
The value of the sum is updated every time one of the two input boxes changes its values.