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.
Related
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')])
The logic behind the dash core component dropdown and the dash bootstrap components is a bit different and I would like to have the best of both worlds: the nice style from dbc and the functionality from dcc. However, modifying the css for the dcc to make it look nicer is complicated and I could not find an existing solution. To set up the dbc component requires some set up effort as each element in the drop down has an own id. Also if you want to directly get from the dropdown what is the selected value (if you ask the dropdown what are you actually showing) you cannot do that directly.
I thus wanted to setup a function that sets the drop down and its callback up automatically but I run into the problem that the callback is a nested function and is therefore not available globally. How can I change that? Or is there another way to build it.
What I want in the end is an easy way to set up the dbc dropdown such that it shows the selected value.
This is what I have so far (the not working solution):
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
def selectable_dropdown(item_id="dropdown",
options=['option_1','option_2']):
# create the items and the ids for each item
dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item)
for item in options]
# create the dropdown menu
dropdown = dbc.DropdownMenu(
dropdown_items,
label="none",
addon_type="prepend",
bs_size="sm",
id=item_id
)
output = Output(item_id, "label")
inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]
#app.callback(output,inputs)
def update_label(*args):
# get the triggered item
ctx = dash.callback_context
triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
# get the label for the triggered id or return no selection
if (np.array([n==None for n in args]).all()) or not ctx.triggered:
return "no selection"
else:
return [label for label in options if item_id+'_'+label == triggered_id]
return dropdown
app = dash.Dash(
external_stylesheets=[dbc.themes.BOOTSTRAP]
)
app.config['suppress_callback_exceptions'] = True
app.layout = \
html.Div([selectable_dropdown(item_id="target_select",
options=["option1 ", "option 2", "option3"])])
if __name__ == "__main__":
app.run_server(debug=False, host = '0.0.0.0', port = 1234)
thats how it should look like (a working example) but I wnat it in a more generalized way and in the best way just in one function or class:
import dash
import numpy as np
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
options=["option1 ", "option 2", "option3"]
item_id = 'dropdown'
dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item)
for item in options]
# create the dropdown menu
dropdown = dbc.DropdownMenu(
dropdown_items,
label="none",
addon_type="prepend",
bs_size="sm",
id=item_id)
output = Output(item_id, "label")
inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]
app = dash.Dash(
external_stylesheets=[dbc.themes.BOOTSTRAP,'./assets/stylesheet.css']
)
app.config['suppress_callback_exceptions'] = True
app.layout = \
html.Div([dropdown])
#app.callback(output,inputs)
def update_label(*args):
# get the triggered item
ctx = dash.callback_context
triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
# get the label for the triggered id or return no selection
if (np.array([n==None for n in args]).all()) or not ctx.triggered:
return "no selection"
else:
return [label for label in options if item_id+'_'+label == triggered_id]
if __name__ == "__main__":
app.run_server(debug=False, host = '0.0.0.0', port = 1234)
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 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
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.