Say I have 3 datasets with different column names. I want to make a plotly dash app where based on which dataset the user selects, it should display the column names in text areas (horizontal strips, sort of like buttons, but not clickable). So far, I’m able to do this, and here’s the code for that:
import pandas as pd
import numpy as np
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
data1 = {'Col1':['A', 'B', 'C', 'D', 'E'],
'Col2':list(np.random.randn(5))}
data2 = {'Col1':['F', 'G', 'H', 'I', 'J'],
'Col2':list(np.random.randn(5))}
data3 = {'Col1':['K', 'L', 'M', 'N', 'O'],
'Col2':list(np.random.randn(5))}
df1 = pd.DataFrame(data1, columns=['Col1', 'Col2'])
df2 = pd.DataFrame(data2, columns=['Col1', 'Col2'])
df3 = pd.DataFrame(data3, columns=['Col1', 'Col2'])
app = dash.Dash()
app.layout = html.Div([
html.Div([dcc.Dropdown(
id='dropdown',
options=[{'label': 'Option 1', 'value': 1},
{'label': 'Option 2', 'value': 2},
{'label': 'Option 3', 'value': 3}],
placeholder='Select the dataset...',
)]),
html.Div(id='output')
])
@app.callback(
dash.dependencies.Output('output', 'children'),
[dash.dependencies.Input('dropdown', 'value')])
def update_output(selected):
if selected==1:
df = df1
elif selected==2:
df = df2
elif selected==3:
df = df3
columns = df.Col1.values
divs=[]
for i in range(len(columns)):
divs.append(
dcc.Graph(
figure=go.Figure(
data=[go.Bar(
x=[10],
y=[columns[i]],
orientation = 'h',
text=['Column number {}: {}'.format(i+1, columns[i])],
textposition = 'inside',
marker = dict(color = 'rgb(158,202,225)'),
)
],
layout=go.Layout(
xaxis=dict(showticklabels=False, fixedrange=True),
yaxis=dict(showticklabels=False, fixedrange=True),
)
),
hoverData='',
style={'width': 400, 'height':250, 'padding': 1},
id='dynamic-text-area-'+format(i)
),
)
divs.append(html.Div(style={'height': '1'}))
return divs
if __name__ == '__main__':
app.run_server(debug=True)
Now, I want to add another functionality that whenever the user hovers over any of these column names and a pie chart should pop up showing some info (I’ll use some simple dummy info in my code below for demonstration purposes). So I added a div in the app layout called pie-chart-div
and a callback inside the loop with the dynamically created text area figure IDs as input to output a pie chart, like so:
for i in range(len(columns)):
'''
###
The rest of the stuff written above
###
'''
@app.callback(dash.dependencies.Output('pie-chart-div', 'children'),
[dash.dependencies.Input('dropdown', 'value'),
dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
def update_graph(df, hoverData):
values = [df[df.Col1==hoverData].Col2.iloc[0], sum(df.Col2)]
labels = ['Selected column','All other columns']
trace = go.Pie(values=values, labels = labels)
fig = dcc.Graph(
id='graph',
figure={
'data': [trace],
'layout': {
'height': 400,
'width': 400,
'showlegend': False
}
}
)
return fig
The problem is, this gives an error saying that it cannot the figure ID in the app layout. And that’s because dash cannot handle dynamic figure creation, as mentioned here. Here’s the full error:
File "C:\Users\h473\Desktop\example_app.py", line 82, in update_output
dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 827, in callback
self._validate_callback(output, inputs, state, events)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 631, in _validate_callback
).replace(' ', ''))
dash.exceptions.NonExistantIdException:
Attempting to assign a callback to the
component with the id "dynamic-text-area-0" but no
components with id "dynamic-text-area-0" exist in the
app's layout.
Here is a list of IDs in layout:
['dropdown', 'output', 'pie-chart-div']
If you are assigning callbacks to components
that are generated by other callbacks
(and therefore not in the initial layout), then
you can suppress this exception by setting
`app.config['suppress_callback_exceptions']=True`.
So, I tried putting in the line:
app.config['suppress_callback_exceptions']=True
but this gives another error saying that pie-chart-div
has already been used and cannot be reused:
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2309, in __call__
return self.wsgi_app(environ, start_response)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\_compat.py", line 35, in reraise
raise value
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\_compat.py", line 35, in reraise
raise value
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 911, in dispatch
return self.callback_map[target_id]['callback'](*args)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 851, in add_context
output_value = func(*args, **kwargs)
File "C:\Users\h473\Desktop\NPS SHAP Dashboard\ex.py", line 902, in update_output
dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 827, in callback
self._validate_callback(output, inputs, state, events)
File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 708, in _validate_callback
output.component_property).replace(' ', ''))
dash.exceptions.CantHaveMultipleOutputs:
You have already assigned a callback to the output
with ID "pie-chart-div" and property "children". An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.
So, how do I fix this and make it work?
Here’s my full code:
import pandas as pd
import numpy as np
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
data1 = {'Col1':['A', 'B', 'C', 'D', 'E'],
'Col2':list(np.random.randn(5))}
data2 = {'Col1':['F', 'G', 'H', 'I', 'J'],
'Col2':list(np.random.randn(5))}
data3 = {'Col1':['K', 'L', 'M', 'N', 'O'],
'Col2':list(np.random.randn(5))}
df1 = pd.DataFrame(data1, columns=['Col1', 'Col2'])
df2 = pd.DataFrame(data2, columns=['Col1', 'Col2'])
df3 = pd.DataFrame(data3, columns=['Col1', 'Col2'])
app = dash.Dash()
app.layout = html.Div([
html.Div([dcc.Dropdown(
id='dropdown',
options=[{'label': 'Dataset 1', 'value': 1},
{'label': 'Dataset 2', 'value': 2},
{'label': 'Dataset 3', 'value': 3}],
placeholder='Select the dataset...',
)]),
html.Div(id='output'),
html.Div(id='pie-chart-div')
])
@app.callback(
dash.dependencies.Output('output', 'children'),
[dash.dependencies.Input('dropdown', 'value')])
def update_output(selected):
if selected==1:
df = df1
elif selected==2:
df = df2
elif selected==3:
df = df3
columns = df.Col1.values
divs=[]
for i in range(len(columns)):
divs.append(
dcc.Graph(
figure=go.Figure(
data=[go.Bar(
x=[10],
y=[columns[i]],
orientation = 'h',
text=['Column number {}: {}'.format(i+1, columns[i])],
textposition = 'inside',
marker = dict(color = 'rgb(158,202,225)'),
)
],
layout=go.Layout(
xaxis=dict(showticklabels=False, fixedrange=True),
yaxis=dict(showticklabels=False, fixedrange=True),
)
),
hoverData='',
style={'width': 400, 'height':250, 'padding': 1},
id='dynamic-text-area-{}'.format(i)
),
)
divs.append(html.Div(style={'height': '1'}))
@app.callback(dash.dependencies.Output('pie-chart-div', 'children'),
[dash.dependencies.Input('dropdown', 'value'),
dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
def update_graph(df, hoverData):
values = [df[df.Col1==hoverData].Col2.iloc[0], sum(df.Col2)]
labels = ['Selected column','All other columns']
trace = go.Pie(values=values, labels = labels)
fig = dcc.Graph(
id='graph',
figure={
'data': [trace],
'layout': {
'height': 400,
'width': 400,
'showlegend': False
}
}
)
return fig
return divs
if __name__ == '__main__':
app.run_server(debug=True)