anechkayf
anechkayf

Reputation: 545

Dash output multiple graph based on user's graph choice

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

Answers (2)

John Collins
John Collins

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)

Your working code as is ā€” single plots @ a time

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)

pie chart names area codes


Dropdown option change:

scatter names area codes


Dropdown option change & data x, y options change:

heatmap address area codes

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.

Modification of code to implement display of up to 5 possible total graphs

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.

three options chosen all five options chosen

ā†’ Use red "x" shown below to clear all:

clear all

resulting in ability to try out different options + graph(s) combos:

cleared app

Upvotes: 1

Harxish
Harxish

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

Related Questions