✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

selectedData retains previous values, after setting selectedpoints to None

Hello everyone,

I have a dashboard with cross-filtering. The issue I am facing is the following:

  1. I filter chart A
  2. I filter chart B
  3. I have a button which resets all the filters, i.e. sets selectedpoints for both charts to None

Everything up to here works just fine.

When I reapply the filter on either of those charts, the other filter gets applied as well. I checked and the “selectedData” callback returns points for both charts, eventhough it has previously been set to None.

My code is below:

import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html

import pandas as pd
import datetime
from dash.dependencies import Input, Output

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

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
total_clicks = 0 # keep track of total clicks
df = pd.read_csv('data.csv').reset_index(drop=True)
df['published']= pd.to_datetime(df['published']) 

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

selections = {
    'sel1' : {'name' : 'crossfilter-error-bar-filetype',  'sort' : True, 'prev' : None, 'field' : 'file_type',       'axis' : 'y', 'data' : 'x', 'orientation' : 'h', 'type' : 'bar'},
    'sel2' : {'name' : 'crossfilter-error-bar-entity',    'sort' : True, 'prev' : None, 'field' : 'entity_type',     'axis' : 'y', 'data' : 'x', 'orientation' : 'h', 'type' : 'bar'},
    'sel3' : {'name' : 'crossfilter-error-bar-operation', 'sort' : True, 'prev' : None, 'field' : 'action',          'axis' : 'y', 'data' : 'x', 'orientation' : 'h', 'type' : 'bar'},
    'sel4' : {'name' : 'crossfilter-error-bar-reason',    'sort' : True, 'prev' : None, 'field' : 'error_type',      'axis' : 'y', 'data' : 'x', 'orientation' : 'h', 'type' : 'bar'},
    'sel5' : {'name' : 'crossfilter-error-pie-attribute', 'sort' : True, 'prev' : None, 'field' : 'error_attribute', 'axis' : 'x', 'data' : 'y', 'orientation' : 'v', 'type' : 'bar'},
    'sel6' : {'name' : 'crossfilter-error-pie-file',      'sort' : True, 'prev' : None, 'field' : 'filename',        'axis' : 'x', 'data' : 'y', 'orientation' : 'v', 'type' : 'bar'},
    'sel7' : {'name' : 'crossfilter-error-line-time',     'sort' : False,'prev' : None, 'field' : 'published',       'axis' : 'x', 'data' : 'y', 'orientation' : 'v', 'type' : 'scatter'}
}

app.layout = html.Div([
    #Row 1 - Input
    html.Div(html.Button('Reset selections', id='reset'), style={'width': '20%', 'display': 'inline-block'}),
    html.Div([html.Label('Password')], style={'width': '10%', 'display': 'inline-block', 'textAlign': 'center'}),
    html.Div([dcc.Input(id='PassInput',type='password')],style={'width': '68%', 'display': 'inline-block'}),
    #Row 2 - Horisontal bars
    html.Div([dcc.Graph(id='crossfilter-error-bar-filetype' , style={'height': 150},)],style={'width': '24%', 'display': 'inline-block'}),
    html.Div([dcc.Graph(id='crossfilter-error-bar-entity'   , style={'height': 150},)],style={'width': '25%', 'display': 'inline-block'}),
    html.Div([dcc.Graph(id='crossfilter-error-bar-operation', style={'height': 150},)],style={'width': '24%', 'display': 'inline-block'}),
    html.Div([dcc.Graph(id='crossfilter-error-bar-reason'   , style={'height': 150},)],style={'width': '25%', 'display': 'inline-block'}),
    #Row 3 - Vertical bars
    html.Div([dcc.Graph(id='crossfilter-error-pie-attribute', style={'height': 200},)],style={'width': '49%', 'display': 'inline-block'}),
    html.Div([dcc.Graph(id='crossfilter-error-pie-file'     , style={'height': 200},)],style={'width': '49%', 'display': 'inline-block'}),
    #Row 4 - Time chart
    html.Div([dcc.Graph(id='crossfilter-error-line-time'    , style={'height': 300},)],style={'width': '98%', 'display': 'inline-block'}),
    #Row 5 - Data table
    html.Div([dash_table.DataTable(id='data_table', 
                                   columns=[{"name": i, "id": i} for i in df.columns],
                                   style_table={'overflowX': 'scroll','overflowY': 'scroll','maxHeight': '600px',},
                                   fixed_rows={ 'headers': True, 'data': 0 })],style={'width': '98%', 'display': 'inline-block'}),
])

@app.callback(
    [Output('crossfilter-error-bar-filetype', 'figure'),
     Output('crossfilter-error-bar-entity', 'figure'),
     Output('crossfilter-error-bar-operation', 'figure'),
     Output('crossfilter-error-bar-reason', 'figure'),
     Output('crossfilter-error-pie-attribute', 'figure'),
     Output('crossfilter-error-pie-file', 'figure'),
     Output('crossfilter-error-line-time', 'figure'),
     Output('data_table', 'data'),
    ],
    [Input('reset', 'n_clicks'),
     Input('crossfilter-error-bar-filetype', 'selectedData'),
     Input('crossfilter-error-bar-entity', 'selectedData'),
     Input('crossfilter-error-bar-operation', 'selectedData'),
     Input('crossfilter-error-bar-reason', 'selectedData'),
     Input('crossfilter-error-pie-attribute', 'selectedData'),
     Input('crossfilter-error-pie-file', 'selectedData'),
     Input('crossfilter-error-line-time', 'selectedData'),
    ])
def display_click_data(n_clicks, *local_selection):
    global total_clicks

    sub = df.copy()

    if n_clicks and n_clicks > total_clicks:
       local_selection = [None for k in local_selection]
       total_clicks = n_clicks
       
    else:
        for idx, item in enumerate(selections):
            if local_selection[idx] and local_selection[idx]['points']:
                axis = selections.get(item).get('axis')
                field = selections.get(item).get('field')
                sub = sub[sub[field].isin([p[axis] for p in local_selection[idx]['points']])]

    return [generate_chart(sub.index, 'sel1', local_selection[0]),
            generate_chart(sub.index, 'sel2', local_selection[1]),
            generate_chart(sub.index, 'sel3', local_selection[2]),
            generate_chart(sub.index, 'sel4', local_selection[3]),
            generate_chart(sub.index, 'sel5', local_selection[4]),
            generate_chart(sub.index, 'sel6', local_selection[5]),
            generate_chart(sub.index, 'sel7', local_selection[6]),
            dt_table(sub.index, None)]

def generate_chart(filter, id, selectedpoints):
    grp = df[df.index.isin(filter)]
    grp = grp.groupby(selections.get(id).get('field'))[selections.get(id).get('field')].count()
    if selections.get(id).get('sort'):
        grp = grp.sort_values()
    
    chart = {
        "data": [{"type": selections.get(id).get('type'),
                'orientation': selections.get(id).get('orientation'),
                selections.get(id).get('data'): grp.values,
                selections.get(id).get('axis'): grp.index,
                'selectedpoints': selectedpoints
                 }],
        "layout": {"title": {"text" : selections.get(id).get('field')},
                   'xaxis' : {"type" : "log"},
                   'clickmode': 'select+event',
                   'dragmode': 'select+event',
                   "margin": {"l": 100, "r": 50, "b": 50, "t": 50, "pad": 4},
                   "xaxis" : {"showticklabels" : False}}
    }
    return chart

def dt_table(filter, selected):
    grp = df[df.index.isin(filter)]
    return grp.to_dict('records')

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