Callback confusion

Hello Dash Community,

As a new user I have read the Dash tutorial numerous times, run many of the examples, and looked for solutions to my particular needs. I am excited to use Dash but I’m having trouble understanding callbacks and the order in which Dash executes.

The code below is the tutorial dcc.Upload example to which I added dcc.Dropdown and dcc.Graph functions. I can successfully read a simple csv file (a few columns with headers and many rows) into a dataframe and display the column headings in the dropdown box. When I try updating the dcc.Graph (which initially displays the first and second columns, named “TIMESTAMP” and “BattV_Min”, of data in my dataframe) through the dropdown box selection, I get a callback error stating that the dataframe name (df) is not defined. I’ve tried and tried without success. Can someone please explain what I am doing wrong?

Thank you.

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.express as px
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
Line_1=“BattV_Min”

app.layout = html.Div([
dcc.Upload(
id=‘upload-data’,
children=html.Div([
'Drag and Drop or ',
html.A(‘Select File’)
]),
style={
‘width’: ‘50%’,
‘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("File loaded: " + filename ),

###----------- Add a dropdown box
dcc.Dropdown(id = “sensor-drop”,
options =[{‘label’: i, ‘value’: i} for i in df],
value=[0],
placeholder = “Select a sensor”,
multi=True,
style = {‘width’:‘70%’}
),

    html.Hr(),  # horizontal line				  

###-------- Add graph -----------
dcc.Graph(id =“sensor-plot”,
figure = px.line(df, x=‘TIMESTAMP’, y=df[Line_1])
),
###--------------------------

    html.Hr(),  # horizontal line

])

###----------Update file selection and dropdown box
@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

###-----------------Update Figure
@app.callback(Output(‘sensor-plot’, ‘value’),
[Input(‘sensor-drop’, ‘value’)])
def update_graph(df):
Line_2 = px.line(df,x = ‘TIMESTAMP’, y=df[value])
fig = px.line(data = [Line_1, Line_2], layout = layout)
return fig

if name == ‘main’:
app.run_server(debug=True)

@jwol20
Please insert your code into Preformatted text so it’s readable. It’s really hard to follow your code in this format, especially this length of code. Also, if you could shorten it by taking out unnecessary lines (such as styling), that would help as well.

Thank you,

Sorry about that, still learning, this is also my first post. I removed all the styling and copying as preformatted text. I’m afraid to edit it further as I fine it introduces more errors. Again, this is basically the Dash tutorial dcc.Upload example with very simple additions of a dropdown box and a graph.

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.express as px
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
Line_1="BattV_Min"

app.layout = html.Div([
    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select File')
        ]),

        # 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("File loaded: " + filename ),

###----------- Add a dropdown box
 dcc.Dropdown(id = "sensor-drop",
                           options =[{'label': i, 'value': i} for i in df],
                           value=[0],
		           placeholder = "Select a sensor",
                           multi=True,			   
		          ),
				  
        html.Hr(),  # horizontal line				  

###-------- Add graph -----------				  
dcc.Graph(id ="sensor-plot", 
                    figure = px.line(df, x='TIMESTAMP', y=df[Line_1])				
		   ),
###--------------------------

        html.Hr(),  # horizontal line

    ])

###----------Update file selection and dropdown box
@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

###-----------------Update Figure
@app.callback(Output('sensor-plot', 'value'),
            [Input('sensor-drop', 'value')])
def update_graph(df):
    Line_2 = px.line(df,x = 'TIMESTAMP', y=df[value])
    figure = px.line(data = [Line_1, Line_2], layout = layout)
    return figure

if __name__ == '__main__':
    app.run_server(debug=True)pe or paste code here

It looks like you’re defining df in the scope of your parse_contents function, not globally, so when you instantiate your Dash components you’re trying to reference a variable that doesn’t exist in its scope. (Actually when Python gets to that point, parse_contents won’t have been executed yet either, just defined, so it doesn’t exist yet in any scope.)

Thank you for your help. If I understand the program flow and your explanation correctly, it goes like this:

  1. Upload box appears

  2. When user selects a file, the first callback “output-data-upload” initiates several things

    a) upload box is updated with the selected file name

    b) “update_output” function is called, selected file is parsed and read (as df)

    c) dropdown box appears showing “select sensor” (df column names show when clicked)

    d) graph appears plotting columns “TIMESTAMP” and “BattV_Min” from df

  3. When user makes a choice from the dropdown box, the second callback “update_graph” fails because df is not defined in this callback, it is not “global”

So, where (or how) in this flow order do I define df so that it will be global and accessible to all callbacks?

Hi,
I am also having trouble with this - trying to define df so that dcc.dropdown can be used to provide interactive figures. Any help would be appreciated.

@msuths1
Try to define your df before the app.layout section. Then, if you plan to make changes to the df, based on the drop-down values that the user chooses, make sure you hose changes happen within the function under the callback decorator.

Hi Adam,
Thanks I got it working! I used the same idea for dcc.Slider but it is producing a slider but it does not produce any marks/ interact with the graph

# POPULATE PRESSURE SLIDER
@app.callback(Output('pressure-slider', 'marks'),
              [Input('data-upload', 'contents')],
              [State('data-upload', 'filename')])
def populate_pressure_slider(contents, filename):
    df = parse_contents(contents, filename)
    min = df['Pressure (bar)'].min(),
    max = df['Pressure (bar)'].max(),
    value = df['Pressure (bar)'].max(),
    marks = {str(pressure): str(pressure) for pressure in df['Pressure (bar)'].unique()}
    return min, max, value, marks

Then I included the slider id in my figure Output callback as in previous examples when not using dcc.Upload.
Any advice would be appreciated.
Thanks!

Hi @msuths1
I would need to see the whole code but I think the “return” part of the function is wrong, because you have only one output in the callback decorator which means only one value should be returned.

Hi @adamschroeder,
I attempted what you suggested however dcc.Slider still does not work.
My simplified code is below:

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
import plotly.graph_objs as go

# APP LAYOUT
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
    html.Div([dcc.Upload(
        id='data-table-upload',
        children=html.Div(['Drag and Drop or ',
                           html.A('Select Files')
                           ]),
        style={
        },
        multiple=False
    ),
        dash_table.DataTable(id='data-table-interact',
                             editable=True,
                             style_data={'height': 'auto'},
                             style_table={'overflowX': 'scroll',
                                          'maxHeight': '300px',
                                          'overflowY': 'scroll'},
                             style_cell={
                                 'minWidth': '0px', 'maxWidth': '180px',
                                 'whiteSpace': 'normal',
                             }
                             ),
        html.Div(id='data-table-container'),
    ]),
    html.Div([
        html.Label(["Select X Variable:",
                    (dcc.Dropdown(id='xaxis', placeholder="Select an option for X", multi=False))
                    ])
    ]),
    html.Div([
        html.Label(["Select Y Variable:",
                    (dcc.Dropdown(id='yaxis', placeholder="Select an option for Y", multi=False))
                    ])
    ]),
    html.Div([
        html.Label(["Select Color Variable",
                    (dcc.Dropdown(id='caxis', placeholder="Select an option for Color", multi=False))])
    ]),
    html.Div([
        html.Label(["Select Size Variable",
                    (dcc.Dropdown(id='saxis', placeholder="Select an option for Size", multi=False))])
    ]),
    html.Div([
        html.Label(["Select Pressure:",
                    dcc.Slider(id='pressure-slider',

                               ), ])
    ], style={'fontSize': 14, 'font-family': 'Arial', 'height': '20%', 'padding': 15,
              'width': '90%'})
])


# READ FILE
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))
        elif 'txt' or 'tsv' in filename:
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')), delimiter=r'\s+'
                             )
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])
    return df


# POPULATE X AXIS DROPDOWN
@app.callback(Output('xaxis', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_xaxis_dropdown(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE Y AXIS DROPDOWN
@app.callback(Output('yaxis', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_yaxis_dropdown(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE C AXIS DROPDOWN
@app.callback(Output('caxis', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_caxis_dropdown(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE S AXIS DROPDOWN
@app.callback(Output('saxis', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_saxis_dropdown(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE PRESSURE SLIDER
@app.callback([Output('pressure-slider', 'marks'),
               Output('pressure-slider', 'min'),
               Output('pressure-slider', 'max'),
               Output('pressure-slider', 'value')],
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_pressure_slider(contents, filename):
    df = parse_contents(contents, filename)
    min = df['Pressure (bar)'].min(),
    max = df['Pressure (bar)'].max(),
    value = df['Pressure (bar)'].max(),
    marks = {str(pressure): str(pressure) for pressure in df['Pressure (bar)'].unique()}
    return min, max, value, marks


# POPULATE DATA TABLE
@app.callback([Output('data-table-interact', 'data'),
               Output('data-table-interact', 'columns')],
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def update_output(contents, filename):
    if contents is None:
        return [{}], []
    df = parse_contents(contents, filename)
    data = df.to_dict('records')
    columns = [{"name": i, "id": i} for i in df.columns]
    return data, columns


# INTERACT FIGURE WITH POPULATED FIELDS
@app.callback(Output('data-table-container', 'children'),
              [Input('data-table-interact', 'data'),
               Input('data-table-interact', 'derived_virtual_data'),
               Input('data-table-interact', 'derived_virtual_selected_rows'),
               Input('xaxis', 'value'),
               Input('yaxis', 'value'),
               Input('caxis', 'value'),
               Input('saxis', 'value'),
               Input('pressure-slider','value')
               ])
def update_figure(rows, derived_virtual_data, derived_virtual_selected_rows, xaxis_name, yaxis_name,
                  marker_color, marker_size,pressure_value):
    df = pd.DataFrame(rows)
    if derived_virtual_selected_rows is None:
        return []
    dff = df if derived_virtual_data is None else pd.DataFrame(derived_virtual_data)
    dfff = dff[dff['Pressure (bar)'] == pressure_value]
    return [
        html.Div([dcc.Graph(id='HTS-graph',
                            figure={'data': [
                                go.Scatter(x=dfff[xaxis_name], y=dfff[yaxis_name],
                                           mode='markers',
                                           marker_color=dfff[marker_color],
                                           marker_size=dfff[marker_size],
                                           marker=dict(
                                               opacity=0.7, showscale=True,
                                               line=dict(width=0.5, color='DarkSlateGrey'),
                                               colorbar=dict(title=marker_color),
                                               colorscale='Viridis', ),
                                           text=dfff['DDEC code'],
                                           hovertemplate=
                                           "<b>%{text}</b><br><br>" +
                                           "Y Variable: %{y:.0f}<br>" +
                                           "X Variable: %{x:. bar}<br>"
                                           "S Variable : %{marker.size:. }<br>" +
                                           "C Variable: %{marker.color:.}"
                                           "<extra></extra>",
                                           )],
                                'layout': go.Layout(
                                    xaxis={'title': xaxis_name, 'autorange': True},
                                    yaxis={'title': yaxis_name, 'autorange': True},
                                    clickmode='event+select',
                                    title="<b><br>MOF Explorer tool </b><br> ",
                                    template="simple_white",
                                    margin={'l': 50, 'b': 60, 't': 90, 'r': 100},
                                    hovermode='closest',
                                     ),

                            },
                            )
                  ], style={'textAlign': 'center', 'width': '70%'})
        for column in [xaxis_name] if column in dfff
        for column in [yaxis_name] if column in dfff
        for column in [marker_color] if column in dfff
        for column in [marker_size] if column in dfff
    ]


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

@msuths1 You should return the values for the slider in the right order, this means the same order as you define in the Output list. Therefore you should write

return marks, min, max, value

Hope this helps.

Aahh… Yes thank you for pointing that out to me! Works fine now :slight_smile: