✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚾️ It's finally Baseball season! Root for the home team... & Register for our Sports Analytics Webinar!

Error loading dependencies when using selectedData in Barchart

Hi,
I’m trying to create a dashboard with some dropdowns to filter but also have the ability to filter by selecting a part of the graph (actually several graphs), but whenever I use selectedData as input I get an Error loading dependencies in the page.
I haven’t got to the point of filtering by selectedData values in the code, I’m stuck when I call the input in the callback.
I’m pasting the whole code so you know where everything is coming from.
Any idea where I got it wrong?

Thanks a mil!

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd 


df = pd.read_csv("item2.txt", sep=',')

app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.config['suppress_callback_exceptions']=True

app.layout = html.Div(
    html.Div(
    children=[
        #First row
        html.Div(
        children=[
            # Location Dropdown
            html.Div(
            children=[
                html.Label('Location'),
                dcc.Dropdown(id='location', 
                    options=[{'label': i, 'value': i} for i in df.Location.unique()], 
                )
            ], className="one columns"),
            
             # Site Dropdown
            html.Div(
            children=[    
                html.Label('Site'),
                dcc.Dropdown(id='site', 
                    options=[{'label': i, 'value': i} for i in df.Site.unique()],
                )
            ], className="one columns"),
    
           # Items
            html.Div(
            children=[    
                html.Label('Items'),
                html.Div(id='items_container')
                    #df.shape[0])
            ], className="one columns"),

           # Quantity
            html.Div(
            children=[    
                html.Label('Quantity'),
                html.Div(id='quantity_container')
            ], className="one columns"),

        ]), 

        html.Div(
        children=[
            html.Div(
            children=[    
                # BarChart with stock by type
                html.Div(id='bar_current_stock_by_type_container'),
            ], className="six columns", style={'width':'80%'}),
        ]),    
        
        html.Div(
        children=[
            html.Div(
            children=[
                # Table with data
                html.Div(id='table-container')
            ], className="twelve columns"),
        ])     
        
    ])
)

def get_filtered_df(location, site):
    if location is None and site is None:
       dff = df
    elif site is None:
        dff = df[df.Location == location]
    elif location is None:
        dff = df[df.Site == site]    
    else:
        dff = df[(df.Location == location) & (df.Site == site)]
    return dff


def generate_table(df, max_rows=10):
    return html.Table(
        [html.Tr([html.Th(col) for col in df.columns])] +
        [html.Tr([html.Td(df.iloc[i][col]) for col in df.columns]) 
        for i in range(min(len(df), max_rows))], id='full_table'
    )


def generate_bar_stock_by_type(df, byStockType):
    line = go.Scatter(
        x=byStockType.sum()['Inventory Value']/1000,
        y=df['Stock Type'],
        name = 'Inventory value (k)'
    )
    bar = go.Bar(
        x= byStockType.count()['Current Balance'], 
        y= df['Stock Type'],
        orientation='h',
        name = 'Num Stock Units'
    )

    graph_stock= dcc.Graph(
        id='bar_stock_by_type',
        figure=go.Figure(
            data= [bar,line],
        ),
        selectedData={'points': [], 'range': None},
        config={'displayModeBar': False},
        style={'width': '40%', 'display': 'inline-block'}
    )
    #print (graph_stock.selectedData)
    return graph_stock



@app.callback(
    dash.dependencies.Output('items_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value'), 
    # This is the line that causes the error 
    dash.dependencies.Input('bar_stock_by_type', 'selectedData')
    ])

def display_metric_items(location, site):
    
    dff = get_filtered_df(location, site)
    return html.H3(dff.shape[0])



@app.callback(
    dash.dependencies.Output('quantity_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value')])


def display_metric_quantity(location, site):
    dff = get_filtered_df(location, site)
    return html.H3(dff['Current Balance'].sum())



# Table
@app.callback(
    dash.dependencies.Output('table-container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value'), 
    ])

def display_table(location, site):
    dff = get_filtered_df(location, site)

    return generate_table(dff)


# Horizontal Bar Chart
@app.callback(
    dash.dependencies.Output('bar_current_stock_by_type_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value')])

def display_bar_current_stock_by_type(location, site):
    dff = get_filtered_df(location, site)

    byStockType = dff.groupby('Stock Type')

    return generate_bar_stock_by_type(dff, byStockType)




if __name__ == '__main__':
    app.run_server(debug=True)
@app.callback(
    dash.dependencies.Output('items_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value'), 
    # This is the line that causes the error 
    dash.dependencies.Input('bar_stock_by_type', 'selectedData')
    ])

def display_metric_items(location, site):

display_metric_items is missing its 3rd input parameter?

Thanks for the reply pdh.
I know it’s missing, it’s because I’m not doing anything with selectedData yet as I can get it to load.
That shouldn’t be a problem though, I don’t think I have to pass the 3 arguments if I’m not using them in the function.

Actually, you do. If you specify 3 Inputs, your callback function will be called with 3 parameters, whether you use them or not.

If I add a dummy dcc.Graph to the static layout, like so:

            html.Div(
            children=[    
                # BarChart with stock by type
                html.Div(id='bar_current_stock_by_type_container',
                         children=[dcc.Graph(id='bar_stock_by_type') ]    # <-- dummy entry
                         ),
            ], className="six columns", style={'width':'80%'}),
        ]),    

the error loading dependencies go away for me … (can’t really test stuff without item2.txt though).

Thanks! You’re right with that dummy entry, it loads if I do that. Then maybe the problem is with the way I’m building the barchart.
Here’s the items2.txt file. If you could have a look I’d really appreciate it.

I haven’t used plotly graphs much, but it seems the problem is in the y-axis of line and bar. How about:

def generate_bar_stock_by_type(df, byStockType):

    x = byStockType.sum()['Inventory Value']/1000
    line = go.Scatter(
        x=x,           # byStockType.sum()['Inventory Value']/1000,
        y=x.index,     # df['Stock Type'],
        name='Inventory value (k)'
    )

    x = byStockType.count()['Current Balance']
    bar = go.Bar(
        x=x,           # byStockType.count()['Current Balance'],
        y=x.index,     # df['Stock Type'],
        orientation='h',
        name='Num Stock Units'
    )

    graph_stock = dcc.Graph(
        id='bar_stock_by_type',
        figure=go.Figure(data=[bar, line]),
        selectedData={'points': [], 'range': None},
        config={'displayModeBar': False},
        style={'width': '40%', 'display': 'inline-block'}
    )
    return graph_stock

Which means you can drop the df parameter, since all you seem to need is the byStockType.

Also updated the display_metric_items function’s signature:

@app.callback(
    dash.dependencies.Output('items_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
     dash.dependencies.Input('site', 'value'), 
     # This is the line that causes the error 
     dash.dependencies.Input('bar_stock_by_type', 'selectedData')
     ])
def display_metric_items(location, site, seldata):
    # seldata not used?
    dff = get_filtered_df(location, site)
    return html.H3(dff.shape[0])

As far as I understand, callbacks are also fired when loading the page for the first time. So the first time around seldata will be None, since the static layout contains a dummy dcc.Graph. You probably could add selectedData to the dummy to avoid a None value on the first callback.

animation

Hi pdh,

Thanks for this. This is showing as normal now and I’m learning a lot.
This filtering was working before the selectedData part though. What I’m trying to do is update the metrics when you select part of the chart with the “Box Select” or “Lasso Select” in the ModeBar. However, when I use that and print selectedData, this is always {u’range’: None, u’points’: []}. So I’m not sure why selectedData is not picking up the selected points.
Any idea?

Looking at some other examples like
https://github.com/plotly/dash-docs/blob/master/tutorial/examples/ and example graph_callbacks_simple.py
they seem to be feeding straight dict’s into figure instead of go.Objects.

So, after changing the setup for graph_stock in callback generate_bar_stock_by_type:

def generate_bar_stock_by_type(df, byStockType):

    x = byStockType.sum()['Inventory Value']/1000
    line = go.Scatter(
        x=x,           # byStockType.sum()['Inventory Value']/1000,
        y=x.index,     # df['Stock Type'],
        name='Inventory value (k)'
    )

    x = byStockType.count()['Current Balance']
    bar = go.Bar(
        x=x,           # byStockType.count()['Current Balance'],
        y=x.index,     # df['Stock Type'],
        orientation='h',
        name='Num Stock Units'
    )

    graph_stock = dcc.Graph(
        id='bar_stock_by_type',
        figure={'data': [bar, line]},  # figure=go.Figure(data=[bar, line]),     #  <---
        selectedData={'points': [], 'range': None},
        config={'displayModeBar': True},
        style={'width': '40%', 'display': 'inline-block'}
    )
    return graph_stock

I get:

WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger PIN: 284-775-916
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:20] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:type(seldata) is <class 'NoneType'>, value is None
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:20] "POST /_dash-update-component HTTP/1.1" 200 -

# selecting points with lasso & box select ... but no joy

WARNING:root:BarChart for None, None
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:20] "POST /_dash-update-component HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:21] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:type(seldata) is <class 'NoneType'>, value is None
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:21] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:type(seldata) is <class 'dict'>, value is {'points': [], 'range': None}
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:21] "POST /_dash-update-component HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:55] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:type(seldata) is <class 'dict'>, value is {'points': [], 'range': None}
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:55] "POST /_dash-update-component HTTP/1.1" 200 -

# Now selecting UK in dropdown ...

WARNING:root:BarChart for UK, None
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:55] "POST /_dash-update-component HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:55] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:type(seldata) is <class 'dict'>, value is {'points': [], 'range': None}
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:03:55] "POST /_dash-update-component HTTP/1.1" 200 -

# And seldata starts to appear

WARNING:root:type(seldata) is <class 'dict'>, value is {'points': [{'x': 1, 'y': 1, 'pointNumber': 1, 'curveNumber': 0}, {'x': 1.74363, 'y': 1, 'pointNumber': 1, 'curveNumber': 1}, {'x': 0.6892903, 'y': 2, 'pointNumber': 2, 'curveNumber': 1}], 'range': {'x': [0.4764138118542687, 2.538404023926047], 'y': ['Power Supplies', 'Cables']}}
INFO:werkzeug:127.0.0.1 - - [02/Mar/2018 14:04:09] "POST /_dash-update-component HTTP/1.1" 200 -

Thanks a mil for that!
I ended up using clickData instead of selectedData because it works very weird with barcharts as you can accidentally select none if you go too far with the selection,etc. The problem with clickData is that I can’t unselect

But still, I don’t understand why the selection/click doesn’t work unless you use one of the dropdowns first. Any idea?

Don’t know. As far as I know, the layout is rendered first and then all callbacks fire immediately to, sort of, initialize the page. Any properties not specified in the layout (like the initial value for your dropdowns Location and Site) are set to None initially.

In that case (location=None and site=None) the get_filtered_df just returns the entire df which is then rendered as the initial graph. I can’t really see a problem here nor a relation with clickData not firing the callback when you click/select data on the initial graph.

Perhaps you could post that as a new question, in the hope someone more familiar with dcc.Graph's will pick up on the question.

To demonstrate:

Starting up

INFO:werkzeug: * Running on http://127.0.0.1:8050/ (Press CTRL+C to quit)
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:56] "GET / HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "GET /_dash-layout HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "GET /_dash-dependencies HTTP/1.1" 200 -
WARNING:root:display-bar, location is None, site is None
WARNING:root:get dff returning df
WARNING:root:generate bar with items 3
WARNING:root:dcc.Graph bar is {'orientation': 'h', 'name': 'Num Stock Units', 'y': Index(['Cables', 'Other', 'Power Supplies'], dtype='object', name='Stock Type'), 'type': 'bar', 'x': Stock Type
Cables            1
Other             2
Power Supplies    5
Name: Current Balance, dtype: int64}
WARNING:root:dcc.Graph line is {'name': 'Inventory value (k)', 'y': Index(['Cables', 'Other', 'Power Supplies'], dtype='object', name='Stock Type'), 'type': 'scatter', 'x': Stock Type
Cables            0.000000
Other             1.754630
Power Supplies    0.808845
Name: Inventory Value, dtype: float64}
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_metric_itm, type(seldata) is <class 'NoneType'>, value is None
WARNING:root:display_metric_itm, location None, site None
WARNING:root:get dff returning df
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_metric_qty, location is None, site is None
WARNING:root:get dff returning df
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_table, location is None, site is None
WARNING:root:get dff returning df
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:04:59] "POST /_dash-update-component HTTP/1.1" 200 -

The callbacks fire in random order, first display_bar, then display_metric_item followed by display_metric_quantity.
(other runs show different orders for these callbacks) When clicking a point on the initial graph (twice), I see:

WARNING:root:display_metric_itm, type(seldata) is <class 'NoneType'>, value is None
WARNING:root:display_metric_itm, location None, site None
WARNING:root:get dff returning df
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:05:30] "POST /_dash-update-component HTTP/1.1" 200 -

WARNING:root:display_metric_itm, type(seldata) is <class 'NoneType'>, value is None
WARNING:root:display_metric_itm, location None, site None
WARNING:root:get dff returning df
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:05:40] "POST /_dash-update-component HTTP/1.1" 200 -

Both of which yield a seldata with a None value (I forgot to change the param name seldata to clickData).

Then selecting UK in the location dropdown:

WARNING:root:display-bar, location is UK, site is None
WARNING:root:get dff returning df[location=]
WARNING:root:generate bar with items 3
WARNING:root:dcc.Graph bar is {'orientation': 'h', 'name': 'Num Stock Units', 'y': Index(['Cables', 'Other', 'Power Supplies'], dtype='object', name='Stock Type'), 'type': 'bar', 'x': Stock Type
Cables            1
Other             1
Power Supplies    4
Name: Current Balance, dtype: int64}
WARNING:root:dcc.Graph line is {'name': 'Inventory value (k)', 'y': Index(['Cables', 'Other', 'Power Supplies'], dtype='object', name='Stock Type'), 'type': 'scatter', 'x': Stock Type
Cables            0.00000
Other             1.74363
Power Supplies    0.68929
Name: Inventory Value, dtype: float64}
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:06:26] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_metric_itm, type(seldata) is <class 'NoneType'>, value is None
WARNING:root:display_metric_itm, location UK, site None
WARNING:root:get dff returning df[location=]
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:06:26] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_metric_qty, location is UK, site is None
WARNING:root:get dff returning df[location=]
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:06:26] "POST /_dash-update-component HTTP/1.1" 200 -
WARNING:root:display_table, location is UK, site is None
WARNING:root:get dff returning df[location=]
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:06:26] "POST /_dash-update-component HTTP/1.1" 200 -

In callback display_metric_item, the seldata is still None. So the previous click on the Graph was not
seen or cached. Now, at this eh… point, after clicking a point:

WARNING:root:display_metric_itm, type(seldata) is <class 'dict'>, value is {'points': [{'pointNumber': 2, 'curveNumber': 1, 'y': 'Power Supplies', 'x': 0.6892903, 'pointIndex': 2}, {'pointNumber': 1, 'curveNumber': 0, 'y': 'Other', 'x': 1, 'pointIndex': 1}]}
WARNING:root:display_metric_itm, location UK, site None
WARNING:root:get dff returning df[location=]
INFO:werkzeug:127.0.0.1 - - [03/Mar/2018 17:06:37] "POST /_dash-update-component HTTP/1.1" 200 -

The display_metric_item callback is triggered again, and seldata shows up having a value

So now I’m a bit lost on the difference between the initial graph and the second one. The only difference
I see is the original is based on an unfiltered df, whereas the second based on dff = df[df.Location == Location]
but both are of type <class 'pandas.core.frame.DataFrame'>, just different lengths. Moreover, adding value='UK'
to the location dropdown in the initial layout doesn’t make any difference whatsoever.

Perhaps some initialisation issue in react?

Thanks again for this. I just posted it as a new question here "clickData only recording clicks after dropdown is used"
It is very strange as even if you clear the filter, it keeps working, it just needs to be used once in order to start working.
Thanks again for your help :slight_smile: