Having problem with updating the figure from the dropdown selected fields

I want to create a webapp that lets you upload a file then visualize it upon selecting the fields you want from the dropdown. The upload works and the dropdown gets update with the new columns name but the figure doesn’t show anything.
Here’s my code:

    > import base64
    > import datetime
    > 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 plotly.graph_objs as go
    > 
    > import pandas as pd
    > 
    > 
    > external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
    > 
    > app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    > app.config['suppress_callback_exceptions'] = True
    > 
    > app.layout = html.Div([
    >     dcc.Upload(
    >         id='upload-data',
    >         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'
    >         },
    >         # Allow multiple files to be uploaded
    >         multiple=True
    >     ),
    >     html.Div(id='output-data-upload'),
    > ])
    > 
    > def parse_contents(contents, filename, date):
    >     content_type, content_string = contents.split(',')
    >     decoded = base64.b64decode(content_string)
    >     try:
    >         if 'csv' in filename:
    >             # Assume that the user uploaded a CSV file
    >             df = pd.read_csv(
    >                 io.StringIO(decoded.decode('utf-8')))
    >         elif 'xls' in filename:
    >             # Assume that the user uploaded an excel file
    >             df = pd.read_excel(io.BytesIO(decoded))
    >     except Exception as e:
    >         print(e)
    >         return html.Div([
    >             'There was an error processing this file.'
    >         ])
    > 
    >     return html.Div([
    >         html.H5(filename),
    >         html.H6(datetime.datetime.fromtimestamp(date)),
    > 
    >         dash_table.DataTable(
    >             data=df.to_dict('rows'),
    >             columns=[{'name': i, 'id': i} for i in df.columns]
    >         ),
    > 
    >         html.Hr(),  # horizontal line
    > 
    >         # For debugging, display the raw contents provided by the web browser
    >         html.Div('Raw Content'),
    >         html.Pre(contents[0:200] + '...', style={
    >             'whiteSpace': 'pre-wrap',
    >             'wordBreak': 'break-all'
    >         }),
    >         dcc.Dropdown(
    >             id='dropdown',
    >             options=[
    >                 {'label':i, 'value': i} for i in df.columns
    >             ],
    >             value=df.columns[0]
    >             
    >             
    >         ),
    >         dcc.Dropdown(
    >             id='dropdown1',
    >             options=[
    >                 {'label':i, 'value': i} for i in df.columns
    >             ],
    >             value=df.columns[1]
    >             
    >             
    >         ),
    >         dcc.Graph(id='main-plot'),
    >     
    >     ])
    > 
    > @app.callback(
    >     Output('main-plot', 'figure'),
    >     [Input('dropdown','value'),
    >     Input('dropdown1','value')])
    > def plotly_maker(col1,col2):
    >     '''
    >     Return a plotly figure
    >     '''
    >     data= []
    > 
    >     for val in range(3):
    >         trace = go.Scatter(
    >             x = df[df['carat']==val][col1],
    >             y = df[df['price']==val][col2],
    >             mode = 'markers'
    > 
    >         )
    >         data.append(trace)
    >     layout = go.Layout()
    >     return {'data': data, 'layout': layout}
    > 
    >     
    > 
    > @app.callback(Output('output-data-upload', 'children'),
    >               [Input('upload-data', 'contents')],
    >               [State('upload-data', 'filename'),
    >                State('upload-data', 'last_modified')])
    > def update_output(list_of_contents, list_of_names, list_of_dates):
    >     if list_of_contents is not None:
    >         children = [
    >             parse_contents(c, n, d) for c, n, d in
    >             zip(list_of_contents, list_of_names, list_of_dates)]
    >         return children 
    > 
    > 
    > 
    > if __name__ == '__main__':
    >     app.run_server(debug=True)

and the console shows

1

1 Like

It looks like df is not defined yet, but the callback plotly_maker is still being fired at startup.

What you can do is put a try except block that checks for a NameError and in the except block you can raise an exception. In this case, the preference would be raise dash.exceptions.PreventUpdate()

Reference: Handle first callback on App start

Hope that fixes this!

1 Like

So, from what I understand, my callbacks are firing together at the start and that what’s causing the problem, but I didn’t quite get how to keep plotly_maker from firing at the start … Like how would I change the code to fix it ? I tried implementing the dash.exceptions.PreventUpdate() but it doesn’t seem to work, I think I’m doing something wrong.

I think the issue is trying to share data between callbacks. Your plotly_maker callback doesnt know what df is. What you need to do is to store the uploaded data in a hidden Div, and read it as an input to your plotly_maker callback. Check more info here.

I am also trying something similar, and having issues with callback data sharing.

1 Like

I tried the hidden div and it didn’t work. I still have the same problem even with a hidden Div…Here’s how I implemented it

    > import base64
    > import datetime
    > 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.config['suppress_callback_exceptions'] = True
    > 
    > app.layout = html.Div([
    >     dcc.Upload(
    >         id='upload-data',
    >         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'
    >         },
    >         # Allow multiple files to be uploaded
    >         multiple=True
    >     ),
    >     html.Div(id='output-data-upload'),
    > ])
    > 
    > def parse_contents(contents, filename, date):
    >     content_type, content_string = contents.split(',')
    >     decoded = base64.b64decode(content_string)
    >     try:
    >         if 'csv' in filename:
    >             # Assume that the user uploaded a CSV file
    >             df = pd.read_csv(
    >                 io.StringIO(decoded.decode('utf-8')))
    >             
    >         elif 'xls' in filename:
    >             # Assume that the user uploaded an excel file
    >             df = pd.read_excel(io.BytesIO(decoded))
    >     except Exception as e: 
    >         print(e)
    >         return html.Div([
    >             'There was an error processing this file.'
    >         ])
    > 
    >     return html.Div([
    >         html.H5(filename),
    >         html.H6(datetime.datetime.fromtimestamp(date)),
    > 
    >         dash_table.DataTable(
    >             data=df.to_dict('rows'),
    >             columns=[{'name': i, 'id': i} for i in df.columns]
    >         ),
    >         
    >         html.Hr(),  # horizontal line
    > 
    >         # For debugging, display the raw contents provided by the web browser
    >         html.Div('Raw Content'),
    >         html.Pre(contents[0:200] + '...', style={
    >             'whiteSpace': 'pre-wrap',
    >             'wordBreak': 'break-all'
    >         }),
    >         
    >         dcc.Dropdown(
    >             id='dropdown',
    >             options=[
    >                 {'label':i, 'value': i} for i in df.columns
    >             ],
    >             value=df.columns[0]
    >             
    >         ),
    >        
    >         dcc.Graph(id='graph'),
    > 
    >         html.Div(id='cache', style={'display': 'none'})
    >     
    >     ])
    > 
    > @app.callback(Output('cache', 'children'), [Input('dropdown', 'value')])
    > def update_cache(value):
    >     filtered_df = df[df['carat'] == value]
    >     return filtered_df.to_json()
    > 
    > 
    > @app.callback(Output('graph', 'figure'), [Input('cache', 'children')])
    > def update_graph(cached_data):
    >     filtered_df = pd.read_json(cached_data)
    >     return {
    >         'data': [{
    >             'x': filtered_df[0],
    >             'y': filtered_df[1],
    >             'type': 'bar'
    >         }]
    >     }
    > 
    > @app.callback(Output('output-data-upload', 'children'),
    >               [Input('upload-data', 'contents')],
    >               [State('upload-data', 'filename'),
    >                State('upload-data', 'last_modified')])
    > def update_output(list_of_contents, list_of_names, list_of_dates):
    >     if list_of_contents is not None:
    >         children = [
    >             parse_contents(c, n, d) for c, n, d in
    >             zip(list_of_contents, list_of_names, list_of_dates)]
    >         return children 
    > 
    > 
    > if __name__ == '__main__':
    >     app.run_server(debug=True)

And I still get name ‘df’ is not defined !

Using your previous code, as I think you made edits using cache, it would look something like this:

 @app.callback(Output('main-plot', 'figure'),
              [Input('dropdown', 'value'),
               Input('dropdown1', 'value')])
 def plotly_maker(col1, col2):
     '''
     Return a plotly figure
     '''

     try:
        data = []

        for val in range(3):
            trace = go.Scatter(
                x=df[df['carat'] == val][col1],
                y=df[df['price'] == val][col2],
                mode='markers')
        data.append(trace)
        layout = go.Layout()

        return {'data': data, 'layout': layout}
    
    except NameError as e:
        raise dash.exceptions.PreventUpdate()

1 Like

that you suggested but it didn’t solve the problem,

Yes it did, you are now running into another problem which is unrelated. This an error with the pandas python package.

I looked over the documentation you linked and I was also able to reproduce the error as well. It is due to the callback firing with an empty dataframe. The program will not crash, it will still work upon uploading a valid file, but it still is an ugly message to see. The workaround I created was to add a conditional to check if the data frame is empty as such:

@app.callback(Output('datatable-upload-graph', 'figure'),
              [Input('datatable-upload-container', 'data')])
def display_graph(rows):
    df = pd.DataFrame(rows)
    if df.empty:
        raise dash.exceptions.PreventUpdate()
    else:
        return {
            'data': [{
                'x': df[df.columns[0]],
                'y': df[df.columns[1]],
                'type': 'bar'
            }]
        }

``
1 Like

After spending sometime trying to figure it out and copy pasting some code that makes a button that as described “propagate table data into the dropdown” I managed to update the graph from the dopdown, but I think I went for the hard way to do it because despite that it’s working, the console is full of Errors and it seems like there’s a much easier way to do it.

import dash_html_components as html
import dash_core_components as dcc
import dash

import plotly
import dash_table_experiments as dte
from dash.dependencies import Input, Output, State

import pandas as pd
import numpy as np

import json
import datetime
import operator
import os

import base64
import io



app = dash.Dash()

app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([

    html.H5("Upload Files"),
    dcc.Upload(
        id='upload-data',
        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'
        },
        multiple=False),
    html.Br(),
    html.Button(
        id='propagate-button',
        n_clicks=0,
        children='Propagate Table Data'
    ),


    html.Br(),
    html.H5("Filter Column"),
    dcc.Dropdown(id='dropdown_table_filterColumn',
        multi = True,
        placeholder='Filter Column'),


    html.Br(),
    html.H5("Updated Table"),
    html.Div(dte.DataTable(rows=[{}], id='table')),
    dcc.Graph(id='datatable-upload-graph')


])


# Functions

# file upload function
def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')

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

    except Exception as e:
        print(e)
        return None

    return df


# callback table creation
@app.callback(Output('table', 'rows'),
              [Input('upload-data', 'contents'),
               Input('upload-data', 'filename')])
def update_output(contents, filename):
    if contents is not None:
        df = parse_contents(contents, filename)
        if df is not None:
            return df.to_dict('records')
        else:
            return [{}]
    else:
        return [{}]


#callback update options of filter dropdown
@app.callback(Output('dropdown_table_filterColumn', 'options'),
              [Input('propagate-button', 'n_clicks'),
               Input('table', 'rows')])
def update_filter_column_options(n_clicks_update, tablerows):
    if n_clicks_update < 1:
        print ("df empty")
        return []

    else:
        dff = pd.DataFrame(tablerows) # <- problem! dff stays empty even though table was uploaded

        print ("updating... dff empty?:"), dff.empty #result is True, labels stay empty

        return [{'label': i, 'value': i} for i in (list(dff))]



@app.callback(Output('datatable-upload-graph', 'figure'),
              [Input('table', 'rows'),
              Input('dropdown_table_filterColumn','value')])
def display_graph(rows, selected):
    ''' 
    update the graph from the dopdown selected columns
    '''
   
    df = pd.DataFrame(rows)
   
    first_selection = selected[0]
    second_selection = selected[1]
    
    
    
    def get_index(name):
        '''
        return the index of a column name
        '''
        for column in df.columns:
            # print(f"{selected2} + {df.columns.get_loc(selected2)}")
            if column == name:
                index = df.columns.get_loc(column)
                # print(index)
                return index
        
    x = get_index(first_selection)
    y = get_index(second_selection)
    
    if df.empty:
        raise dash.exceptions.PreventUpdate()
    else:
        return {
            'data': [{
                'x': df[df.columns[x]],
                'y': df[df.columns[y]],
                'type': 'bar'
            }]
        }

app.css.append_css({
    "external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"
})

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