Reputation: 545
I've been trying to understand this for days. I'm asking a user to input X_axis data, Y_axis data, and a graph they want to display.
My current version displays only one graph based on the user's choice. I want it to be able to display multiple graphs simultaneously(ex: pie and line charts). I added 'multi=True' to have an option to choose multiple graphs(commented out right now as it gives the error: "Callback error updating my_graph.figure", UnboundLocalError: local variable 'fig' referenced before assignment). I know I need to create multiple Outputs from the callback functions but I can't figure out how. Can someone please help me out? Thanks!!
import dash
import dash_core_components as dcc
import dash_html_components as HTML
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
from dash.exceptions import PreventUpdate
df_data = pd.read_json("test.json")
app = dash.Dash(__name__)
app.layout = html.Div([
html.P("Choose data1:"),
dcc.Dropdown(
id='x_axis',
options=[{'value': x, 'label': x}
for x in df_data.keys()],
clearable=False,
style={'width':'40%'}
),
html.P("Choose data2:"),
dcc.Dropdown(
id='y_axis',
options=[{'value': x, 'label': x}
for x in df_data.keys()],
clearable=False,
style={'width':'40%'}
),
html.P("Choose a graph to display:"),
dcc.Dropdown(
id='graph',
options=[{'value': 'pie', 'label': 'Pie chart'},
{'value': 'line', 'label': 'Line chart'},
{'value': 'bar', 'label': 'Bar chart'},
{'value': 'scatter', 'label': 'Scatter chart'},
{'value': '2dhistogram', 'label': '2dhistogram chart'}],
clearable=False,
style={'width':'40%'},
#multi=True
),
dcc.Graph(id='my_graph', figure={}),
])
@app.callback(
Output("my_graph", "figure"),
[Input("x_axis", "value"),
Input("y_axis", "value"),
Input("graph", "value")])
def generate_chart(x_axis, y_axis, graph):
if not x_axis:
raise PreventUpdate
if not y_axis:
raise PreventUpdate
if not graph:
raise PreventUpdate
dff = df_data
if graph=="pie":
fig = px.pie(dff, values=y_axis, names=x_axis, title="Pie Chart")
elif graph=="line":
fig = px.line(dff, x=x_axis, y=y_axis, title="Line Chart")
elif graph=="bar":
fig = px.bar(dff, x=x_axis, y=y_axis, title="Bar Chart")
elif graph=="scatter":
fig = px.scatter(dff, x=x_axis, y=y_axis, title="Scatter Chart")
elif graph=="2dhistogram":
fig = px.density_heatmap(dff, x=x_axis, y=y_axis, nbinsx=20, nbinsy=20,
color_continuous_scale="Viridis", title="2D Histogram Chart")
else:
fig = px.pie(dff, values=y_axis, names=x_axis, title="Pie Chart")
return fig
app.run_server(debug=True)
Sample json file:
{
"Names": {
"0": "Alice",
"1": "Robert",
"2": "Garry",
"3": "Nate",
"4": "Karen",
"5": "Nick"
},
"Address": {
"0": "21 Main St",
"1": "19 Third St",
"2": "4 Church St",
"3": "5 High St",
"4": "9 Elm St",
"5": "06 Washingtom St"
},
"AreaCode": {
"0": "777",
"1": "421",
"2": "768",
"3": "345",
"4": "888",
"5": "123"
}}
Upvotes: 2
Views: 2977
Reputation: 2961
Well you are close, the code you provide works great and sets most everything needed up correctly! (few minor things needed correction which I show first below)
import dash
from dash import dcc
from dash import html
import pandas as pd
import plotly.express as px
from dash.dependencies import Input
from dash.dependencies import Output
from dash.exceptions import PreventUpdate
df_data = pd.read_json("test.json")
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.P("Choose data1:"),
dcc.Dropdown(
id="x_axis",
options=[{"value": x, "label": x} for x in df_data.keys()],
clearable=False,
style={"width": "40%"},
),
html.P("Choose data2:"),
dcc.Dropdown(
id="y_axis",
options=[{"value": x, "label": x} for x in df_data.keys()],
clearable=False,
style={"width": "40%"},
),
html.P("Choose a graph to display:"),
dcc.Dropdown(
id="graph",
options=[
{"value": "pie", "label": "Pie chart"},
{"value": "line", "label": "Line chart"},
{"value": "bar", "label": "Bar chart"},
{"value": "scatter", "label": "Scatter chart"},
{"value": "2dhistogram", "label": "2dhistogram chart"},
],
clearable=False,
style={"width": "40%"},
# multi=True
),
dcc.Graph(id="my_graph", figure={}),
]
)
@app.callback(
Output("my_graph", "figure"),
[
Input("x_axis", "value"),
Input("y_axis", "value"),
Input("graph", "value"),
],
)
def generate_chart(x_axis, y_axis, graph):
if not x_axis:
raise PreventUpdate
if not y_axis:
raise PreventUpdate
if not graph:
raise PreventUpdate
dff = df_data
if graph == "pie":
fig = px.pie(dff, values=y_axis, names=x_axis, title="Pie Chart")
elif graph == "line":
fig = px.line(dff, x=x_axis, y=y_axis, title="Line Chart")
elif graph == "bar":
fig = px.bar(dff, x=x_axis, y=y_axis, title="Bar Chart")
elif graph == "scatter":
fig = px.scatter(dff, x=x_axis, y=y_axis, title="Scatter Chart")
elif graph == "2dhistogram":
fig = px.density_heatmap(
dff,
x=x_axis,
y=y_axis,
nbinsx=20,
nbinsy=20,
color_continuous_scale="Viridis",
title="2D Histogram Chart",
)
else:
fig = px.pie(dff, values=y_axis, names=x_axis, title="Pie Chart")
return fig
app.run_server(debug=True, dev_tools_hot_reload=True)
Dropdown option change:
Dropdown option change & data x, y options change:
All I had to change were a couple indentation mistakes, the importing of the dash component library html
(lower case not upper), and yeah otherwise it just needed a little fixing as far as proper indentation (which may have just been a copy paste onto SO issue) ā great job! Of course it doesn't really make any sense to display pie charts for area codes because they are nominal values, not truly quantitative measurements, but as far as proof of principle goes for making a decently complex interactive Dash web app you set it up all correctly, it seems to me.
But now for the multiple graphs at once...a few more changes will be needed.
Now this isn't 100% ideal (e.g., it'd be better if no blank graphs were displayed) but hopefully it helps get you on the right track and progressing with the customization of your project š
import sys
import dash
from dash import dcc
from dash import html
from dash import no_update
import pandas as pd
import plotly.express as px
from dash.dependencies import Input
from dash.dependencies import Output
from dash.exceptions import PreventUpdate
df_data = pd.read_json("test.json")
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.P("Choose data1:"),
dcc.Dropdown(
id="x_axis",
options=[{"value": x, "label": x} for x in df_data.keys()],
clearable=False,
style={"width": "40%"},
),
html.P("Choose data2:"),
dcc.Dropdown(
id="y_axis",
options=[{"value": x, "label": x} for x in df_data.keys()],
clearable=False,
style={"width": "40%"},
),
html.P("Choose a graph to display:"),
dcc.Dropdown(
id="graph",
options=[
{"value": "pie", "label": "Pie chart"},
{"value": "line", "label": "Line chart"},
{"value": "bar", "label": "Bar chart"},
{"value": "scatter", "label": "Scatter chart"},
{"value": "2dhistogram", "label": "2dhistogram chart"},
],
clearable=True,
style={"width": "40%"},
multi=True,
),
dcc.Graph(id="my_graph_1", figure={}),
dcc.Graph(id="my_graph_2", figure={}),
dcc.Graph(id="my_graph_3", figure={}),
dcc.Graph(id="my_graph_4", figure={}),
dcc.Graph(id="my_graph_5", figure={}),
]
)
@app.callback(
[
Output("my_graph_1", "figure"),
Output("my_graph_2", "figure"),
Output("my_graph_3", "figure"),
Output("my_graph_4", "figure"),
Output("my_graph_5", "figure"),
],
[
Input("x_axis", "value"),
Input("y_axis", "value"),
Input("graph", "value"),
],
)
def generate_chart(x_axis, y_axis, graph):
if not all([x_axis, y_axis]):
raise PreventUpdate
if not graph:
return [{}] * 5
dff = df_data
graphs = []
print(graph, file=sys.stderr) # used for debugging help
if "pie" in graph:
fig = px.pie(dff, values=y_axis, names=x_axis, title="Pie Chart")
graphs.append(fig)
if "line" in graph:
fig = px.line(dff, x=x_axis, y=y_axis, title="Line Chart")
graphs.append(fig)
if "bar" in graph:
fig = px.bar(dff, x=x_axis, y=y_axis, title="Bar Chart")
graphs.append(fig)
if "scatter" in graph:
fig = px.scatter(dff, x=x_axis, y=y_axis, title="Scatter Chart")
graphs.append(fig)
if "2dhistogram" in graph:
fig = px.density_heatmap(
dff,
x=x_axis,
y=y_axis,
nbinsx=20,
nbinsy=20,
color_continuous_scale="Viridis",
title="2D Histogram Chart",
)
graphs.append(fig)
graphs += (5 - len(graphs)) * [{}] # or can use `[no_update]` here
g1, g2, g3, g4, g5 = graphs
return g1, g2, g3, g4, g5
app.run_server(debug=True, dev_tools_hot_reload=True)
Let me know if you have any questions about what I did ā I think it's pretty self-explanatory from the code itself (I basically just made four more graph objects in the layout, and return 5 graphs in the callback -- if a user hasn't chosen all five options, then the difference between the number of dropdown options chosen and total graphs possible (five) is returned as "no_update
"'s. I switched the clearable
parameter of the dcc.Dropdown
to true, and this allows graphs to disappear. Just eliminating an option after having chosen it wont eliminate that graph (it just results in a no_update
being sent), but if you clear all with the "x" mark in the dropdown, then you can erase all graphs and start over with new options. As I'm sure you're aware, not all possible options for "data1" and "data2" result in possible graphs.
Upvotes: 1
Reputation: 459
You have added multi=True
to get multiple inputs from the user, it still doesn't change the fact that the function will only return a figure object with a single plot.
I feel subplots is the solution.
You can create subplots like this
fig = make_subplots(rows=1, cols=len(graph))
counter = 1
Then use individual if conditions and add traces, by using a counter.
if "scatter" in graph:
fig.add_trace(
go.Scatter(x=dff['x_axis'], y=dff['y_axis']),
row=1, col=counter )
counter += 1
if "pie" in graph:
fig.add_trace(
go.Pie(labels=dff['x_axis'], values=dff['y_axis']),
row=1, col=counter )
counter += 1
...
...
...
Upvotes: 0