Dynamic Filter in Dash python

Hi all.
I would like to create a dynamic filter as the redmine page here:


It will allows users can custom the filter. When user click to add filter button, it will show list name column in data that we want to filter.
Are there any ways to do it ?
Please help me.
Thanks

So basically you need a main dropdown with options in it and then you use it to return other dropdown. Below is my sample code. Hope this help:

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc

app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])

app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            html.H6('Add filter'),
            dcc.Dropdown(id='main_dropdown',
                         options=[
                             {'label': 'New York City', 'value': 'New York City'},
                             {'label': 'Montreal', 'value': 'Montreal'},
                             {'label': 'San Francisco', 'value': 'San Francisco'},
                         ],
                         value=[])
        ],width={'size':3,'offset':0,'order':1}),
        dbc.Col([
            html.Div(id='sub_dropdown_1')
        ],width={'size':3,'offset':0,'order':1})
    ], className='p-2 align-items-stretch')
])

@app.callback(Output('sub_dropdown_1', 'children'),
             [Input('main_dropdown', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == 'New York City':
        return html.H6('Filters'), dcc.Dropdown(
                         options=[
                             {'label': 'Harlem', 'value': 'Harlem'},
                             {'label': 'Chinatown', 'value': 'Chinatown'},
                             {'label': 'Flushing', 'value': 'Flushing'},
                         ],
                         value='Harlem')
    elif main_dropdown == 'Montreal':
        return html.H6('Filters'), dcc.Dropdown(
                         options=[
                             {'label': 'Old Montreal', 'value': 'Old Montreal'},
                             {'label': 'Downtown Montreal', 'value': 'Downtown Montreal'},
                             {'label': 'Mile End', 'value': 'Mile End'},
                         ],
                         value='Old Montreal')

    elif main_dropdown == 'San Francisco':
        return html.H6('Filters'), dcc.Dropdown(
                         options=[
                             {'label': 'Financial District', 'value': 'Financial District'},
                             {'label': 'Mission District', 'value': 'Mission District'},
                             {'label': 'Japan Town', 'value': 'Japan Town'},
                         ],
                         value='Japan Town')
    else:
        return html.Div()
    
    
if __name__ == "__main__":
    app.run_server(debug=False,port=1115)

P/s: Are you Vietnamese?

1 Like

Hi Hoa, I’m Vietnamese. Thanks for your solution , but I want to create multi filter It means when I click add filter, it will create new filter as here: Issues - Redmine

Sure, @phuonghao145,

It looks like you could benefit from pattern-matching callbacks, check it out:

With this, you can add dynamic amounts of elements that all can target the same output, without having to customize your code too much.

1 Like

@jinnyzor: I see that her requirement is more complicated that after choosing value from first filter, it will return another filter based on it. So I did something as below but I didn’t figure out how to disable options after choosing and keep the Checkbox as her example. Can you give me a look and a suggestion. Or can you give us a sample with pattern matching. Thank you.

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc

app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])

app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            dcc.Dropdown(id='main_dropdown',
                         options=[
                             {'label': 'Status', 'value': 'Status'},
                             {'label': 'Tracker', 'value': 'Tracker'},
                             {'label': 'Priority', 'value': 'Priority'},
                         ],
                         value='Status',
                        disabled=False)
        ],width={'size':3,'offset':0,'order':1}),
        dbc.Col([
            dbc.Checkbox(label="Status",
            value=True,
            id="checklist-input",
        )
        ],width={'size':1,'offset':0,'order':1}),
        dbc.Col([
            html.Div(id='sub_dropdown_1')
        ],width={'size':3,'offset':0,'order':1})
    ], className='p-2 align-items-center'),
    
    dbc.Row([
        dbc.Col([
            html.Div(id='checklist-input-2')
        ],width={'size':1,'offset':3,'order':1}),
        dbc.Col([
            html.Div(id='sub_dropdown_2')
        ],width={'size':3,'offset':0,'order':1})        
    ], className='p-2 align-items-center'),
    dbc.Row([
        dbc.Col([
            html.Div(id='checklist-input-3')
        ],width={'size':1,'offset':3,'order':1}),
        dbc.Col([
            html.Div(id='sub_dropdown_3')
        ],width={'size':3,'offset':0,'order':1})        
    ], className='p-2 align-items-center'),
])

@app.callback(Output('sub_dropdown_1', 'children'),
             [Input('checklist-input', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == True:
        return html.Div(
            dbc.Row([
            dcc.Dropdown(
                options=[
                    {'label': 'New', 'value': 'New'},
                    {'label': 'Confirmed', 'value': 'Confirmed'},
                    {'label': 'Closed', 'value': 'Closed'},
                ],
                value='New')
            ])
        )
    else:
        return html.Div()
    
    
@app.callback(Output('checklist-input-2', 'children'),
             [Input('main_dropdown', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == 'Tracker':
        return dbc.Checkbox(label="Tracker",
            value=True,
            id="checklist-input-2-2",
        )
    else:
        return html.Div()    

@app.callback(Output('sub_dropdown_2', 'children'),
             [Input('checklist-input-2-2', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == True:
        return html.Div(
            dbc.Row([
            dcc.Dropdown(
                options=[
                    {'label': 'Defect', 'value': 'Defect'},
                    {'label': 'Feature', 'value': 'Feature'},
                    {'label': 'Pass', 'value': 'Pass'},
                ],
                value='Pass')
        ])
        )
    else:
        return html.Div()   
    
@app.callback(Output('checklist-input-3', 'children'),
             [Input('main_dropdown', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == 'Priority':
        return dbc.Checkbox(label="Priority",
            value=True,
            id="checklist-input-3-2",
        )
    else:
        return html.Div()    

@app.callback(Output('sub_dropdown_3', 'children'),
             [Input('checklist-input-3-2', 'value')])

def update_options_1(main_dropdown):
    if main_dropdown == True:
        return html.Div(
            dbc.Row([
            dcc.Dropdown(
                options=[
                    {'label': 'Low', 'value': 'Low'},
                    {'label': 'Normal', 'value': 'Normal'},
                    {'label': 'High', 'value': 'High'},
                ],
                value='Normal')
            ])
        )
    else:
        return html.Div()     
    
    
if __name__ == "__main__":
    app.run_server(debug=False,port=1115)

1 Like

Hello @hoatran / @phuonghao145,

After looking at their layouts, I decided to use what they are using as well, which is a table that is being populated. This is the result:

And here is the code:

import dash
from dash import html, dcc, Input, Output, State, ALL, dash_table, ctx
import dash_bootstrap_components as dbc
import pandas as pd

app = dash.Dash(__name__)

df = pd.DataFrame({
    'A':[str(i) for i in range(10)],
    'B':[str(i) for i in range(10)],
    'C':[str(i) for i in range(10)],
    'D':[str(i) for i in range(10)],
    'E':[str(i) for i in range(10)],
    'F':[str(i) for i in range(10)],
})

def addFilter(fil = None):
    if not fil:
        fil = 'A'
    newDiv = html.Tr([
        html.Td(dcc.Checklist(id=f'{fil}_check', options=[fil])),
        html.Td(dcc.Dropdown(id=f'{fil}_drop', options=['is', 'is not'])),
        html.Td(dcc.Dropdown(id=f'{fil}_value', options=df[fil].unique())),
    ], style={'width':'75%'})
    return newDiv

app.layout = html.Div([
    html.Fieldset(
        [html.Legend('Filters'),
        html.Table([addFilter()], id='filters', style={'width':'75%'}),
         html.Div(['Add Filter: ',
         dcc.Dropdown(id='addFilter', options=[{'label':i, 'value': i} if i != 'A'
                                               else {'label':i, 'value': i, 'disabled':True} for i in df.columns],
                      style={'width':'150px', 'margin':'5px'}),
        ], style={'width':'250px', 'display': 'flex',
                  'justify-content:':'space-around'}),
         ],
        id='filterField', style={'display':'flex', 'justify-content:':'space-between',
                                 'width':'100%'}),
    html.Fieldset([
        html.Legend('options'),
        html.Button('apply', id='apply'),
        html.Button('clear', id='clear')
    ]),
    html.Fieldset([
        html.Legend('data'),
        dash_table.DataTable(id='myTable', data=df.to_dict('records'),
                             columns=[{'name':i, 'id':i } for i in df.columns])
    ])
])


@app.callback(Output('filters', 'children'),
    Output('addFilter', 'options'),
    Input('addFilter', 'value'),
    Input('clear','n_clicks'),
    State('addFilter', 'options'),
    State('filters', 'children'),
    prevent_initial_call=True
)
def update_options(fil, n1, options, children):
    if ctx.triggered_id == 'clear':
        children = []
        children.append(addFilter())
        for opt in options:
            if fil == opt['value']:
                opt['disabled'] = False
    else:
        if fil:
            for opt in options:
                if fil == opt['value']:
                    opt['disabled'] = True
            children.append(addFilter(fil))
    return children, options

@app.callback(
    Output('myTable', 'data'),
    Input('apply', 'n_clicks'),
    Input('clear', 'n_clicks'),
    State('filters', 'children'),
    prevent_initial_call = True
)
def applyFilters(n1, n2, children):
    new_df = df.copy()
    if ctx.triggered_id == 'clear':
        return new_df.to_dict('records')
    if n1:
        for child in children:
            if 'value' in child['props']['children'][0]['props']['children']['props']:
                if 'value' in child['props']['children'][2]['props']['children']['props']:
                    if 'value' in child['props']['children'][1]['props']['children']['props']:
                        if child['props']['children'][1]['props']['children']['props']['value'] == 'is':
                            new_df = new_df[
                                new_df[child['props']['children'][0]['props']
                                ['children']['props']['id'].split('_')[0]] == child['props']['children']
                                [2]['props']['children']['props']['value']]
                        else:
                            new_df = new_df[
                                new_df[child['props']['children'][0]['props']
                                ['children']['props']['id'].split('_')[0]] != child['props']['children']
                                [2]['props']['children']['props']['value']]
        return new_df.to_dict('records')


if __name__ == "__main__":
    app.run_server(debug=True, port=1115)

Pattern-matching callbacks would have worked if, we were just testing to see if there was a dropdown value. This is slightly more complicated, because you are testing to see if the value should be used or not. This would have been a lot harder to accomplish via pattern-matching due to the nature of making sure the row stayed together.

2 Likes

Amazing, thank you alot.

1 Like

thank you very much

1 Like

Hi @jinnyzor , I have 2 question :

  1. Is there a way to save the filter parameter to the url?
  2. I want a new option ‘between’ not only ‘is / is not’.
    when i select ‘is/is not’ it shows a dropdown that allows to select the value of the corresponding column
    and when i choose between it appear 2 dropdown menus that allow to choose the value of the respective column

Please help me . Thanks

Hello @phuonghao145,

1 - Yes, you can, however, there is a lot going on to try to put the parameters in the url. Filters on and off, is/is not and then the value. Add in the second, and there is quite a bit to parse through.
2 - It’s better to display this option for only specific columns, so a list of columns that you want to give this ability to will be needed. When between, you mean not including or including the values given?