I’ve got a dropdown that allows me to filter a categorical plotly graph. A separate callback allows the user to alter this graph from a bar chart to a pie chart. This part works as expected.
I’ve got separate slider components that adjust the settings for both the bar chart or the pie chart. The problem is the sliders are fixed in place. Is it possible to add/remove the relevant sliders based on the selection chosen in the radio items?
If the bar chart is selected (see below), only the two bar sliders should be visible. The other two should be dropped or removed. Conversely, if the pie is chosen, to the opposite should occur. The bar sliders are removed in place of the pie sliders.
The layout on the filtering div should stay the same.
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
df = pd.DataFrame({
'Fruit': ['Apple','Banana','Orange','Kiwi','Lemon'],
'Value': [1,2,4,8,6],
})
external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets = external_stylesheets)
filter_box = html.Div(children=[
html.Div(children=[
html.Label('Fruit', style = {}),
dcc.Dropdown(
id = 'value_type',
options = [
{'label': x, 'value': x} for x in df['Fruit'].unique()
],
value = df['Fruit'].unique(),
multi = True,
clearable = True
),
html.Label('Cat Chart', style = {'display': 'inline','paddingTop': '0rem', "justifyContent": "center"}),
dcc.RadioItems(['Bar','Pie'],'Bar',
id = 'catmap',
),
html.Label('Bar Transp', style = {'display': 'inline-block', 'paddingTop': '0.1rem',}),
dcc.Slider(0, 1, 0.2,
value = 0.6,
id = 'bar_transp'),
html.Label('Bar Width', style = {'display': 'inline-block'}),
dcc.Slider(200, 1000, 200,
value = 600,
id = 'bar_width'),
html.Label('Pie Transp', style = {'display': 'inline-block', 'paddingTop': '0.1rem',}),
dcc.Slider(0, 1, 0.2,
value = 0.6,
id = 'pie_transp'),
html.Label('Pie Hole', style = {'display': 'inline-block'}),
dcc.Slider(0, 1, 0.2,
value = 0.4,
id = 'pie_hole'),
], className = "vstack",
)
])
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
dbc.Row([
dbc.Col(html.Div(filter_box),
),
]),
]),
dbc.Col([
dbc.Row([
dcc.Graph(id = 'type-chart'),
]),
])
])
], fluid = True)
@app.callback(
Output('type-chart', 'figure'),
[Input('value_type', 'value'),
Input('catmap', 'value'),
Input('bar_transp', 'value'),
Input('bar_width', 'value'),
Input('pie_transp', 'value'),
Input('pie_hole', 'value'),
])
def chart(value_type, catmap, bar_transp, bar_width, pie_transp, pie_hole):
dff = df[df['Fruit'].isin(value_type)]
if catmap == 'Bar':
df_count = dff.groupby(['Fruit'])['Value'].count().reset_index(name = 'counts')
if df_count.empty == True:
type_fig = go.Figure()
else:
df_count = df_count
type_fig = px.bar(x = df_count['Fruit'],
y = df_count['counts'],
color = df_count['Fruit'],
opacity = bar_transp,
width = bar_width
)
elif catmap == 'Pie':
df_count = dff.groupby(['Fruit'])['Value'].count().reset_index(name = 'counts')
if df_count.empty == True:
type_fig = go.Figure()
else:
df_count = df_count
type_fig = px.pie(df_count,
values = df_count['counts'],
opacity = pie_transp,
hole = pie_hole
)
return type_fig
if __name__ == '__main__':
app.run_server(debug = True)
Pattern-matching callbacks are not needed in this situation, it could be though if the filters were to be created dynamically. Instead, in order to add/remove the relevant sliders based on the selected graph type, you would wrap them in a component (as children
) and use a callback to update the children
property of that component.
However, I suggest you just show/hide the sliders rather than add/remove them so that their state is preserved across graph type updates :
-
Use one wrapper component for each type (here ‘filter_bar’ and ‘filter_pie’) :
filter_box = html.Div(children=[ html.Div(children=[ html.Label('Fruit', style = {}), dcc.Dropdown( id = 'value_type', options = [ {'label': x, 'value': x} for x in df['Fruit'].unique() ], value = df['Fruit'].unique(), multi = True, clearable = True ), html.Label('Cat Chart', style = {'display': 'inline','paddingTop': '0rem', "justifyContent": "center"}), dcc.RadioItems(['Bar','Pie'],'Bar', id = 'catmap', ), html.Div(id='filter_bar', children=[ html.Label('Bar Transp', style = {'display': 'inline-block', 'paddingTop': '0.1rem',}), dcc.Slider(0, 1, 0.2, value = 0.6, id = 'bar_transp'), html.Label('Bar Width', style = {'display': 'inline-block'}), dcc.Slider(200, 1000, 200, value = 600, id = 'bar_width'), ]), html.Div(id='filter_pie', children=[ html.Label('Pie Transp', style = {'display': 'inline-block', 'paddingTop': '0.1rem',}), dcc.Slider(0, 1, 0.2, value = 0.6, id = 'pie_transp'), html.Label('Pie Hole', style = {'display': 'inline-block'}), dcc.Slider(0, 1, 0.2, value = 0.4, id = 'pie_hole'), ]) ], className = "vstack", ) ])
-
And update its
style
property in a callback :@app.callback( Output('filter_bar', 'style'), Output('filter_pie', 'style'), Input('catmap', 'value') ) def display_sliders(catmap): if catmap == 'Bar': return {}, {'display': 'none'} if catmap == 'Pie': return {'display': 'none'}, {} return {'display': 'none'}, {'display': 'none'}
4