How to plot bar graph using two multi dropdowns?

Hi, I want to create a web app with two multi dropdowns to plot bar graph. One of the multi dropdowns is for the x-axis and another one is for the y-axis of the bar graph. Can anyone teach me how to do it?

This is my code:

import base64
import io
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Upload(
        id='datatable-upload',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%', 'height': '60px', 'lineHeight': '60px',
            'borderWidth': '1px', 'borderStyle': 'dashed',
            'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px'
        },
    ),
    dash_table.DataTable(id='datatable-upload-container'),
    html.Div(id='output-data-upload'),
    dcc.Dropdown(
        id='data_selector1',
        options=[
            {}
        ],
        multi=True,
        value=['Location_X']
    ),
    dcc.Dropdown(
        id='data_selector2',
        options=[
            {}
        ],
        multi=True,
        value=['Location_Y']
    ),
    dcc.Graph(id='datatable-upload-graph')
])


def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    if 'csv' in filename:
        # Assume that the user uploaded a CSV file
        return pd.read_csv(
            io.StringIO(decoded.decode('utf-8')))
    elif 'xls' in filename:
        # Assume that the user uploaded an excel file
        return pd.read_excel(io.BytesIO(decoded))


@app.callback(Output('data_selector1', 'options'),
              [Input('datatable-upload-container', 'data')])
def update_dropdown(rows):
    df = pd.DataFrame(rows)
    print('updating menus')
    columns = df.columns
    col_labels = [{'label': k, 'value': k} for k in columns]
    return col_labels


@app.callback(Output('data_selector2', 'options'),
              [Input('datatable-upload-container', 'data')])
def update_dropdown1(rows):
    df = pd.DataFrame(rows)
    print('updating menus')
    columns = df.columns
    col_labels = [{'label': k, 'value': k} for k in columns]
    return col_labels


@app.callback(Output('datatable-upload-container', 'data'),
              [Input('datatable-upload', 'contents')],
              [State('datatable-upload', 'filename')])
def update_output(contents, filename):
    if contents is None:
        return [{}]
    df = parse_contents(contents, filename)
    data = df.to_dict('records')
    return data


@app.callback(Output('datatable-upload-graph', 'figure'),
              [Input('data_selector1', 'value'),
               Input('data_selector2', 'value')],
              [State('datatable-upload-container', 'data')])
def display_graph(value1, value2, rows):
    df = pd.DataFrame(rows)
    return {
        'data': [{
            # first column that was selected
            'x': df[value1],
            # second column that was selected
            'y': df[value2],
            'type': 'bar'
        }]
    }


if __name__ == '__main__':
    app.run_server(debug=True)

I get three error popping up after i run the code:

options[0].label in Dropdown with ID “data_selector2” is required but it was not provided.

options[0].label in Dropdown with ID “data_selector1” is required but it was not provided.

Callback error updating datatable-upload-graph.figure
KeyError: “None of [Index([‘Location_X’], dtype=‘object’)] are in the [columns]”

Hi @Yuechean, the errors are of two different kinds:

  • for the dropdowns, you need to provide a non-empty dictionary for the options, so you need to initialize with dummy values (see below)
  • before the datatable has been uploaded, the dataframe you’re using in your callbacks is empty, resulting in indexing errors (KeyError).

The modified code does not trigger errors any more

import base64
import io
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
from dash.exceptions import PreventUpdate


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Upload(
        id='datatable-upload',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%', 'height': '60px', 'lineHeight': '60px',
            'borderWidth': '1px', 'borderStyle': 'dashed',
            'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px'
        },
    ),
    dash_table.DataTable(id='datatable-upload-container'),
    html.Div(id='output-data-upload'),
    dcc.Dropdown(
        id='data_selector1',
        options=[
            {'label': '', 'value': ''}
        ],
        multi=True,
        value=['Location_X']
    ),
    dcc.Dropdown(
        id='data_selector2',
        options=[
            {'label': '', 'value': ''}
        ],
        multi=True,
        value=['Location_Y']
    ),
    dcc.Graph(id='datatable-upload-graph')
])


def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    if 'csv' in filename:
        # Assume that the user uploaded a CSV file
        return pd.read_csv(
            io.StringIO(decoded.decode('utf-8')))
    elif 'xls' in filename:
        # Assume that the user uploaded an excel file
        return pd.read_excel(io.BytesIO(decoded))


@app.callback(Output('data_selector1', 'options'),
              [Input('datatable-upload-container', 'data')])
def update_dropdown(rows):
    if rows is None:
        raise PreventUpdate
    df = pd.DataFrame(rows)
    print('updating menus')
    columns = df.columns
    col_labels = [{'label': k, 'value': k} for k in columns]
    return col_labels


@app.callback(Output('data_selector2', 'options'),
              [Input('datatable-upload-container', 'data')])
def update_dropdown1(rows):
    if rows is None:
        raise PreventUpdate
    df = pd.DataFrame(rows)
    print('updating menus')
    columns = df.columns
    col_labels = [{'label': k, 'value': k} for k in columns]
    return col_labels


@app.callback(Output('datatable-upload-container', 'data'),
              [Input('datatable-upload', 'contents')],
              [State('datatable-upload', 'filename')])
def update_output(contents, filename):
    if contents is None:
        raise PreventUpdate
    df = parse_contents(contents, filename)
    data = df.to_dict('records')
    return data


@app.callback(Output('datatable-upload-graph', 'figure'),
              [Input('data_selector1', 'value'),
               Input('data_selector2', 'value')],
              [State('datatable-upload-container', 'data')])
def display_graph(value1, value2, rows):
    if rows is None or value1 is None or value2 is None:
        raise PreventUpdate
    df = pd.DataFrame(rows)
    return {
        'data': [{
            # first column that was selected
            'x': df[value1],
            # second column that was selected
            'y': df[value2],
            'type': 'bar'
        }]
    }


if __name__ == '__main__':
    app.run_server(debug=True)

1 Like