Home > Software design >  Dash output multiple graph based on user's graph choice
Dash output multiple graph based on user's graph choice

Time:10-21

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"
}}

CodePudding user response:

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
         ...
         ...
         ...

CodePudding user response:

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

  • Related