I have a Dash App with multiple tabs. Is there a way to share a dataframe between tabs i.e. sharing a dataframe created in tab 1 with tab 2
Check out the chapter on “sharing data between callbacks” - https://plot.ly/dash/sharing-data-between-callbacks
Hi @chriddyp, thank you for the response. I guess the problem I am facing is not completely solvable using hidden divs.
For Ex:
- In Tab 1, I am storing data in a hidden div and then using that data, I am creating a bar chart
- Next, I navigate to Tab 2 and show a line graph based on the same hidden data again.
- However, when I navigate back to the first tab, the stored data in the hidden div is lost.
Below is the sample code.
import dash
from dash.dependencies import Input, Output, State, Event
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
import json
tab1_layout = [html.Div("Storing data to hidden div and displaying as bar chart"),
html.Button("Store data into hidden div",
n_clicks=0,
id='store-data-hidden-div'),
html.Button("Display data from hidden div as bar chart",
n_clicks=0,
id='create-bar-chart'),
dcc.Graph(id = "bar-graph")
]
tab2_layout = [html.Div("retrieving data from hidden div and displaying as line chart"),
html.Button("Display data from hidden div as line chart",
n_clicks=0,
id='create-line-chart'),
dcc.Graph(id = "line-graph")
]
app = dash.Dash()
app.config['suppress_callback_exceptions'] = True
## css file
app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
app.layout = html.Div([
# hidden Div for storing data needed for graphs
html.Div(id='graph-data-json-dump', style={'display': 'none'}),
# Title
html.Div("Sharing data between callbacks"),
# tabs
dcc.Tabs(
tabs=[
{'label': 'Tab 1', 'value': 1},
{'label': 'Tab 2', 'value': 2}
],
value=1,
id='tabs'
),
# Tab-layout
html.Div(id='tab-layout')
])
#switching between tabs
@app.callback(dash.dependencies.Output('tab-layout', 'children'),
[dash.dependencies.Input('tabs', 'value')])
def call_tab_layout(tab_value):
if tab_value == 1:
return tab1_layout
elif tab_value == 2:
return tab2_layout
else:
html.Div()
#sending data to hidden div from tab1
@app.callback(dash.dependencies.Output('graph-data-json-dump', 'children'),
[dash.dependencies.Input('store-data-hidden-div', 'n_clicks')])
def store_data_to_hidden_div(nclick):
if nclick >0:
df = pd.DataFrame({'x_axis':[6,4,9],
'y_axis':[4,2,7]})
return df.to_json(orient = 'split')
#plotting bar graph in same tab
@app.callback(dash.dependencies.Output('bar-graph', 'figure'),
[dash.dependencies.Input('graph-data-json-dump', 'children'),
dash.dependencies.Input('create-bar-chart', 'n_clicks')])
def create_bar_chart(json_dump, nclick):
if nclick >0:
json_dump = json.loads(json_dump)
json_dump_df = pd.DataFrame(json_dump['data'], columns=json_dump['columns'])
data = [go.Bar(
x = json_dump_df['x_axis'].values,
y = json_dump_df['y_axis'].values,
name = "Bar Chart")]
fig = go.Figure(data = data)
return fig
#plotting line graph in next tab
@app.callback(dash.dependencies.Output('line-graph', 'figure'),
[dash.dependencies.Input('graph-data-json-dump', 'children'),
dash.dependencies.Input('create-line-chart', 'n_clicks')])
def create_line_chart(json_dump, nclick):
if nclick > 0:
json_dump = json.loads(json_dump)
json_dump_df = pd.DataFrame(json_dump['data'], columns=json_dump['columns'])
data = [go.Scatter(
x=json_dump_df['x_axis'].values,
y=json_dump_df['y_axis'].values,
name="Line Chart")]
fig = go.Figure(data=data)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Thanks for the example! I see what’s happening now.
The problem is in this callback. Here’s what’s happening:
- When you navigate back to Tab 1, you are returning
tab1_layout
-
tab1_layout
returns a two buttons wheren_clicks=0
- Since the callback returns new components with IDs, Dash fires the appropriate callbacks. In this case, it fires the callbacks with
n_clicks=0
for the newly rendered buttons, therefore resetting the data that you stored previously in your hidden div.
You have a couple of options:
- Prevent the update from happening by raising an exception in your callback when
n_clicks=0
- Move the
Button
that triggers the datastore out oftab1_layout
and keep it stored persistently throughout the app.
Hi chriddyp, I had the same problem. could you create example of how to do with your first option? many thanks.
Also, what is odd is when I switched between pages, only the html table shows the data of previous load, all figures are gone. How to let the figures show?
I have a similar problem @chriddyp
I’m using two tabs with two different graphs, Graphs get updated dynamically using callbacks.
When I navigate between tabs, and come back to the first tab, the graph is gone. Is there a way to handle this scenario to retain data in the layout when you switch back between tabs.
Appreciate you time and help.
Thanks!
@chriddyp, Thanks for all the wonderful work you do and for the great examples.
I am facing this problem as well: storing data in a hidden div, but losing it upon navigating across tabs. Do you mind providing code examples of the options you suggest?
Just curious if anyone found a solution to this. I’m having the same problem.
Same problem here. =/
I’ve already tried to use persistence as parameter and dcc.Store(), but nothing worked.
Hi @chriddyp,
I am only starting to use Dash and I am facing the same issue. Could you please provide an example for these two (or one of them!) that you mentioned?
Where should we prevent the update to happen? In the callback that is used to switch from one tab to the other? Here is the callback that I am using :
@app.callback(
Output("tabs_content", "children")
, Input("tabs_app", "value"))
def callback2(tab):
if tab == "tab-1":
return tab_1
elif tab == "tab-2":
return tab_2
Thanks a lot!
Mathieu
Hi,
I was searching for something similar and I think what @chriddyp refers to, is this one:
from dash.exceptions import PreventUpdate
The update in the applicable callback can be prevented by doing this:
if nclick == 0:
raise PreventUpdate
I also changed the imports, used prevent_initial_call=True
, and changed from Input
to State
when using the graph-data-json-dump
.
For the sake of completeness, here the full code from the initial post:
import dash
from dash import dcc, html, Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd
import plotly.graph_objs as go
import json
tab1_layout = [
html.Div("Storing data to hidden div and displaying as bar chart"),
html.Button(
"Store data into hidden div",
n_clicks=0,
id='store-data-hidden-div'
),
html.Button(
"Display data from hidden div as bar chart",
n_clicks=0,
id='create-bar-chart'
),
dcc.Graph(id="bar-graph")
]
tab2_layout = [
html.Div("retrieving data from hidden div and displaying as line chart"),
html.Button(
"Display data from hidden div as line chart",
n_clicks=0,
id='create-line-chart'),
dcc.Graph(id="line-graph")
]
app = dash.Dash(
__name__,
suppress_callback_exceptions=True,
)
app.layout = html.Div(
[
# hidden Div for storing data needed for graphs
html.Div(id='graph-data-json-dump', style={'display': 'none'}),
# Title
html.Div("Sharing data between callbacks"),
# tabs
dcc.Tabs(
id='tabs',
children=[
dcc.Tab(label='Tab 1', value='t1'),
dcc.Tab(label='Tab 2', value='t2'),
],
value='t1',
),
# Tab-layout
html.Div(id='tab-layout')
]
)
# switching between tabs
@app.callback(
Output('tab-layout', 'children'),
Input('tabs', 'value'),
)
def call_tab_layout(tab_value):
if tab_value == 't1':
return tab1_layout
elif tab_value == 't2':
return tab2_layout
else:
html.Div()
# sending data to hidden div from tab1
@app.callback(
Output('graph-data-json-dump', 'children'),
Input('store-data-hidden-div', 'n_clicks'),
prevent_initial_call=True
)
def store_data_to_hidden_div(nclick):
if nclick == 0:
raise PreventUpdate
df = pd.DataFrame(
{'x_axis': [6, 4, 9],
'y_axis': [4, 2, 7]
}
)
return df.to_json(orient='split')
# plotting bar graph in same tab
@app.callback(
Output('bar-graph', 'figure'),
State('graph-data-json-dump', 'children'),
Input('create-bar-chart', 'n_clicks'),
prevent_initial_call=True
)
def create_bar_chart(json_dump, nclick):
content = json.loads(json_dump)
json_dump_df = pd.DataFrame(content['data'], columns=content['columns'])
data = go.Bar(
x=json_dump_df['x_axis'].values,
y=json_dump_df['y_axis'].values,
name="Bar Chart"
)
fig = go.Figure(data=data)
return fig
# plotting line graph in next tab
@app.callback(
Output('line-graph', 'figure'),
State('graph-data-json-dump', 'children'),
Input('create-line-chart', 'n_clicks'),
prevent_initial_call=True
)
def create_line_chart(json_dump, nclick):
json_dump = json.loads(json_dump)
json_dump_df = pd.DataFrame(json_dump['data'], columns=json_dump['columns'])
data = [go.Scatter(
x=json_dump_df['x_axis'].values,
y=json_dump_df['y_axis'].values,
name="Line Chart")]
fig = go.Figure(data=data)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Anyways, I think the better way to handle this would be using a dcc.Store
component.