Reputation: 459
I don't want my dropdown to close after choosing a value, I want to it to stay opened on my page. I'm using dcc.Dropdown
.
dcc.Dropdown(id='job-type', options=self.options,
placeholder='Select one or more Event(s)',
value=self.job_type, multi=True)
Upvotes: 2
Views: 2041
Reputation: 71
I found this alternative dash library which does not auto close after multi selection
https://www.dash-mantine-components.com/components/multiselect
For those still looking for this feature, I thought it is worth mentioning!
Upvotes: 1
Reputation: 2961
How about instead another possible approach, a dcc.Checklist
paired with an html.Summary
(which leverages built-in collapsibility, thus, mimicking a dropdown menu) within an html.Details
component? This much more closely satisfies what you are asking for - a kind of dropdown menu which wont automatically close itself after each selection of any of its listed options.
E.g.,
A local file called "jobs.csv", tab-delimited, with the following contents:
code options job_type
13-2011.00 Accountants and Auditors Business and Financial Operations
27-2011.00 Actors Arts, Design, Entertainment, Sports, and Media
15-2011.00 Actuaries Computer and Mathematical
29-1291.00 Acupuncturists Healthcare Practitioners and Technical
55-1011.00 Air Crew Officers Military Specific
23-1022.00 Arbitrators, Mediators, and Conciliators Legal
17-1011.00 Architects, Except Landscape and Naval Architecture and Engineering
19-2011.00 Astronomers Life, Physical, and Social Science
33-3011.00 Bailiffs Protective Service
51-3011.00 Bakers Production
39-5011.00 Barbers Personal Care and Service
15-2099.01 Bioinformatics Technicians Computer and Mathematical
25-1042.00 Biological Science Teachers, Postsecondary Educational Instruction and Library
19-1029.00 Biological Scientists, All Other Life, Physical, and Social Science
19-4021.00 Biological Technicians Life, Physical, and Social Science
19-1029.04 Biologists Life, Physical, and Social Science
51-8013.03 Biomass Plant Technicians Production
11-3051.04 Biomass Power Plant Managers Management
15-2041.01 Biostatisticians Computer and Mathematical
15-1299.07 Blockchain Engineers Computer and Mathematical
47-2011.00 Boilermakers Construction and Extraction
In layout.py:
children = [
html.Details(
[
html.Div(
[
dcc.Checklist(
id="jobs-multi-dropdown",
options=[
{"label": f"{job_title}", "value": f"{job_type}"}
for (job_title, job_type) in zip(
df_jobs.options, df_jobs.job_type
)
],
)
],
className="updates-list",
),
html.Summary(
html.Code(f"✔ JOBS"),
style={"color": "rgb(24, 230, 112)"},
className="updates-header",
),
],
id="jobs-selection",
),
html.Br(),
html.Br(),
html.Div(
[html.Button("Submit", id="jobs-selected", n_clicks=0)],
style={"display": "flow-root"},
),
html.Br(),
html.H3("Job Types Selected:"),
html.Code(id="job-type"),
html.Br(),
]
In callbacks.py:
@app.callback(
Output("job-type", "children"),
[Input("jobs-selected", "n_clicks"), State("jobs-multi-dropdown", "value")],
)
def choose_job(n_click, job_types):
""" Returns interactively the associated job "type"
"""
if job_types:
return [f"{n} {job_type}, " for (n, job_type) in enumerate(job_types)]
else:
return ["Select any number of jobs from the list above."]
To close the "dropdown", you simply re-click the "✅ Jobs" rounded component, which is the html.Summary
dash component. And the "details" are the dcc.Checklist
.*
*Usually there is by default an arrow, or triangle rather, symbol which "twists" (I guess apparently is the common term: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) which helps to signal to the user that you can click on this to expand it. For some reason in my screenshots it has been made invisible but just copying the code I provide, it should show up.
In assets/custom.css:
@import url('https://fonts.googleapis.com/css?family=Cinzel:300,400,500,600,700|Muli:200,300,400,500,600|Open+Sans:200,300,400,500,600|Oswald:200,300,400,500,600&subset=latin-ext');
@import url('https://fonts.googleapis.com/css?family=Montserrat:200,200i,300,300i,400,400i,500,500i');
@import url('https://fonts.googleapis.com/css?family=Raleway:300,400,500,600,800&subset=latin-ext');
@import url('https://fonts.googleapis.com/css?family=Roboto+Mono:200,300,400,500');
@keyframes glow{0%{text-shadow:0 0 5px rgba(255,255,255,.5),0 0 5px rgba(255,255,255,.5),0 0 5px rgba(3,242,255,.5),0 0 30px rgba(0,230,128,0.4706),0 0 5px rgba(255,235,59,.5),0 0 5px rgba(0,0,255,.5),0 0 15px rgba(3,242,255,.5)}50%{text-shadow:0 0 5px rgba(0,0,255,.75),0 0 5px rgba(238,130,238,.75),0 0 5px rgba(187,77,255,0.549),0 0 30px rgba(77,255,192,.75),0 0 5px rgba(255,235,59,.75),0 0 5px rgba(128,0,128,.75),0 0 15px rgba(187,77,255,0.549)}75%{text-shadow:0 0 5px rgba(255,165,0,.25),0 0 5px rgba(255,165,0,.25),0 0 5px rgba(230,0,115,.25),0 0 30px rgba(230,0,115,.25),0 0 5px rgba(255,235,59,.25),0 0 5px rgba(255,0,0,.25),0 0 15px rgba(230,0,115,.25)}100%{text-shadow:0 0 20px rgba(127,255,0,.5),0 0 20px rgba(0,255,0,.5),0 0 10px rgba(255,255,0,.5),0 0 20px rgba(255,193,7,.5),0 0 10px rgba(255,255,0,.5),0 0 20px rgba(255,215,0,.5),0 0 20px rgba(77,255,192,.5)}}
h1 { font-size: 3.5rem; font-family: 'Montserrat'; text-rendering: optimizeLegibility; color: #0d04a5; font-weight: 500; text-decoration: none; border-bottom: 0.0px solid gray; line-height: 4rem; text-decoration: underline }
h2 { font-family: 'Oswald', serif; color: var(--pph-color-8); cursor: default; font-weight: 300; font-size: 2rem; }
h3 { font-size: 2.0rem; font-family: 'Montserrat', sans-serif; font-weight: 300; color: rgb(32, 92, 188); cursor: default }
h4 { font-size: 1.5rem; font-family: 'Oswald', sans-serif; color: var(--pph-color-57); font-weight: 400; cursor: default }
h5 { font-size: 1.2rem; font-family: 'Muli', sans-serif; cursor: default }
h6 { font-size: 1.1rem; color: #333; font-weight: 400 }
@media (min-width:550px) input[type="checkbox"], input[type="radio"] {
details#jobs-selection { display: inline-block; width: 80%; }
ol.updates-list > li { margin-bottom: 5px; padding-left: 3%; margin-left: 8%; margin-right: 5%; list-style-type: auto; list-style-position: outside }
summary { cursor: pointer }
text-rendering:optimizeLegibility; -moz-appearance: none; display: inline-block; background-color: #f1f1f1; color: #666; top: 10px; height: 30px; width: 30px; border: 0; border-radius: 50px; cursor: pointer; margin-right: 7px; outline: none; }
.updates-list { font-family: 'Roboto Mono'; font-size: .75rem; color: #064d56f0; text-align: left; width: 30%; margin-left: 35%; padding: 10px; padding-bottom: 24px; border: 1px solid #3f51b54d; box-sizing: border-box; box-shadow: 0px 10px 25px -12px black; max-height: 400px; overflow: auto }
.updates-header:hover { animation: glow 2.5s infinite cubic-bezier(0.38, 0.39, 0.5, 0.51); }
.updates-header { font-weight: 500; border: 1px solid rgba(0, 0, 200, .33); width: 32%; border-radius: 30px; margin-left: 34%; box-sizing: border-box; display: block; text-align: center; margin-bottom: -2px; background-color: #00000085; letter-spacing: 8px; box-shadow: 0px 0px 8px -1px #00ff55ab; padding: 12px; padding-right: 2px; }
@media (min-width:550px)
.button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover {
border-color: #00FFC050; background: #00000075; }
@media (min-width:550px)
.button, button, input[type="submit"], input[type="reset"], input[type="button"] {
display: inline-block; padding: 0 25px; color: #000000; text-align: center; font-size: 14px; font-weight: 500; font-family: "Cinzel", serif !important; line-height: 32px; text-decoration: none; white-space: nowrap; background-color: #ffffffcc !important; border-radius: 30px; border: 1px ridge #00000050; cursor: pointer; box-sizing: border-box; }
ORIGINAL SUGGESTION
(included just for reference, and, to also still show what's possible using the
dcc.Dropdown
component and the slightly different UX which can be implemented that way - it does have the advantage of being searchable and clearable)
Yes, there is indeed a specific dcc.Dropdown
parameter "multi" which can be set to boolean True, and that should work to allow your users to select multiple options from the dropdown.
EDIT: Searching is enabled as default, so it's pretty fast and convenient to simply click the dropdown bar once to extend its options, either scroll and select with another mouse click (and then yes unfortunately an additional mouse click [by default behavior] is required to re-expand the list of options]) or, user can just begin typing the first letters of each option they want, and they will appear highlighted. So, typing text also re-expands the dropdown list. You can just press enter to add any highlighted option from the dropdown, and then continue typing for the next selection because the cursor's focus will have remained in the dropdown component text search field. It may be possible to hack/override the default CSS/JS behavior of the menu automatically closing after each selection, but it may be a bit tricky. I could try and help figure that out with you if that's what you really think is your necessary desired functionality for your UX.
In your layout file:
html.Br(),
html.H2("Jobs"),
dcc.Dropdown(
id="jobs-multi-dropdown",
value=None,
clearable=True,
optionHeight=50,
multi=True,
options=[
{"label": f"{job_title}", "value": f"{job_type}"}
for (job_title, job_type) in zip(df_jobs.options, df_jobs.job_type)
],
placeholder="—🔍⤑Search all Jobs—",
),
html.Div(
[html.Button("Submit", id="jobs-selected", n_clicks=0)],
style={"display": "flow-root"},
),
html.Br(),
html.H3("Job Types Selected:"),
html.Code(id="job-type"),
html.Br(),
I am not sure exactly what you want done with the "type" information, but I created a callback triggered by a "submit" button and which also takes as State
-type input the current value(s) selected from the dropdown, just to demonstrate.
You could add something like this to your callbacks.py
file:
@app.callback(
Output("job-type", "children"),
[Input("jobs-selected", "n_clicks"), State("jobs-multi-dropdown", "value")],
)
def choose_job(n_click, job_types):
""" Returns interactively the associated job "type"
"""
if job_types:
return [f"{n} {job_type}, " for (n, job_type) in enumerate(job_types)]
else:
return ["Select any number of jobs from the list above."]
which results in:
Notice: As any item becomes selected, it is automatically removed from the dropdown remaining options. -
And if you're curious here's some of the CSS, I am not sure this alone would work but it may help introduce you to the customizability available in Dash if you aren't already (this would go in a .css file located under a folder called "assets" & Dash will automatically find it and override its defaults with your own customizations):
@import url('https://fonts.googleapis.com/css?family=Cinzel:300,400,500,600,700|Muli:200,300,400,500,600|Open+Sans:200,300,400,500,600|Oswald:200,300,400,500,600&subset=latin-ext');
@import url('https://fonts.googleapis.com/css?family=Montserrat:200,200i,300,300i,400,400i,500,500i');
@import url('https://fonts.googleapis.com/css?family=Raleway:300,400,500,600,800&subset=latin-ext');
@import url('https://fonts.googleapis.com/css?family=Roboto+Mono:200,300,400,500');
h1 {
font-size: 3.5rem;
font-family: 'Montserrat', sans serif;
text-rendering: optimizeLegibility;
color: #0d04a5;
font-weight: 500;
text-decoration: none;
border-bottom: 0.0px solid gray;
line-height: 4rem;
text-decoration: underline
}
h2 {
font-family: 'Oswald', serif;
color: #0a7fc2;
cursor: default;
font-weight: 300;
font-size: 2rem;
}
h3 {
font-size: 2.0rem;
font-family: 'Montserrat', sans-serif;
font-weight: 300;
color: rgb(32, 92, 188);
cursor: default
}
h4 {
font-size: 1.5rem;
font-family: 'Oswald', sans-serif;
color: #1fadac;
font-weight: 400;
cursor: default
}
h5 {
font-size: 1.2rem;
font-family: 'Muli', sans-serif;
cursor: default
}
h6 {
font-size: 1.1rem;
color: #333;
font-weight: 400
}
.is-focused .Select-input>input {
background-color: rgba(66, 66, 66, 0.46) !important;
color: #46ffbb;
margin-bottom: 1px;
mix-blend-mode: hard-light;
}
.is-focused:not(.is-open)>.Select-control {
cursor: pointer !important;
border-color: rgba(10, 80, 250, 0.85);
color: #0F00C6;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 0 3px #46FFBB;
}
.is-open .Select-arrow, .Select-arrow-zone:hover>.Select-arrow {
border-top-color: #666;
}
.is-open>.Select-control .Select-arrow {
top: -2px;
border-color: transparent transparent #999;
border-width: 0 5px 5px
}
.is-open>.Select-control {
border-color: #46ffbb #46ffefc7 #46ff6cd4 !important;
border-radius: 5px !important;
border-width: 3px;
}
.is-searchable.is-focused:not(.is-open)>.Select-control {
cursor: text !important
}
.is-searchable.is-open>.Select-control {
cursor: pointer !important;
background: rgba(255, 255, 255, 0.18) !important;
}
.button, button, input[type="submit"], input[type="reset"], input[type="button"] {
display: inline-block;
padding: 0 25px;
color: #000000;
text-align: center;
font-size: 14px;
font-weight: 500;
font-family: "Cinzel", serif !important;
line-height: 32px;
text-decoration: none;
white-space: nowrap;
background-color: #ffffffcc !important;
border-radius: 30px;
border: 1px ridge #00000050;
cursor: pointer;
box-sizing: border-box
}
.button.button-primary, button.button-primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary {
color: #00000075 !important;
background-color: #33C3F050;
border-color: #33C3F0
}
.button.button-primary:hover, button.button-primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus {
color: #00000075 !important;
background-color: transparent;
border-color: #1EAEDB
}
.button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus {
color: #5C5D86;
border-color: #00000075 !important
}
.button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover {
border-color: #00FFC050;
background: #00000075
}
.Select.is-clearable.is-searchable.Select--multi {
width: 70 %;
display: inline-block;
margin-left: 15%;
}
.Select-placeholder {
margin-left: -12%;
background: transparent !important;
}
Upvotes: 1
Reputation: 61214
Based on:
I don't want my dropdown to close after choosing a value, I want to it to stay opened on my page.
... it seems to me that you're actually looking for the features and functionalities of a dcc.Checklist
:
import dash_core_components as dcc
dcc.Checklist(
options=[
{'label': 'New York City', 'value': 'NYC'},
{'label': 'Montréal', 'value': 'MTL'},
{'label': 'San Francisco', 'value': 'SF'}
],
value=['NYC', 'MTL']
)
Which in this case would produce:
And this would do exactly what you're describing with regards to functionality:
Here's an example that uses the built-in px.stocks
dataset:
This particular example will only replicate the already existing functionalities of the figure legend, but this setup should work as a good starting point if you'd like to free yourself of the limitations of using the legend.
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# jupyterdash setup
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])
# data and figure setup
df = px.data.stocks()
df = df.set_index('date')
fig1 = px.line(df, x = df.index, y = df.columns, template = 'plotly_dark')
fullnames = {'GOOG':'Google',
'AAPL': 'Apple',
'AMZN': 'Amazon',
'FB':'Facebook',
'NFLX':'Netflix',
'MSFT':'Microsoft'}
# app layout
app.layout = dbc.Container([
dbc.Row([
# https://hackerthemes.com/bootstrap-cheatsheet/
dbc.Col([html.H1("DropDown < CheckList", className = 'text-left text-success'), ], width = 8),
]),
dbc.Row([dbc.Col([dcc.Checklist(id = 'Check1',
options=[{"label": fullnames[col], "value": col} for col in df.columns],
value=df.columns),
], width = 2),
dbc.Col([dcc.Graph(id='Graph1', figure = fig1)], width = 10),
],
),
])
# interactivity through callbacks
@app.callback(
Output('Graph1', 'figure'),
[Input('Check1', 'value')])
def subset_graph(value):
dfs = df[value]
fig2 = px.line(dfs, x = dfs.index, y = dfs.columns, template = 'plotly_dark')
return fig2
app.run_server(mode='inline', port = 9099)
Following up on a comment from John Collins, I've wrapped the dcc.Checklist
into a html.Div
with the following setup to make the checklist scrollable when there are to many items to be displayed at the same time:
html.Div(dcc.Checklist(id = 'Check1',
options=[{"label": col, "value": col} for col in df.columns],
value=df.columns,
labelStyle={'display': 'inline-block', 'width': '12em', 'line-height':'0.5em'}
),
style = {"overflow-y":"scroll",
"overflow-x":'hidden',
"height": '480px'
}
)
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
import numpy as np
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# jupyterdash setup
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])
# data and figure setup
# data
start = 1980
ncols = 40
nrows = 365
cols = [str(i) for i in np.arange(start, start+ncols)]
df = pd.DataFrame(np.random.randint(-1,2, (nrows,ncols)), columns = cols).cumsum()
df.iloc[0] = 0
# figure
fig1 = px.line(df, x=df.index, y=cols,
# width=820,
height=480,
template = 'plotly_dark'
)
# app layout
app.layout = dbc.Container([
dbc.Row([
# https://hackerthemes.com/bootstrap-cheatsheet/
dbc.Col([html.H1("DropDown < CheckList", className = 'text-left text-success', style = {'padding': 25})], width = 8),
]),
dbc.Row([dbc.Col([html.Div(dcc.Checklist(id = 'Check1',
options=[{"label": col, "value": col} for col in df.columns],
value=df.columns,
labelStyle={'display': 'inline-block', 'width': '12em', 'line-height':'0.5em'}
),
style = {"overflow-y":"scroll",
"overflow-x":'hidden',
"height": '480px'
}
)], width = 3),
dbc.Col([dcc.Graph(id='Graph1', figure = fig1)], width = 8),
],
),
])
# interactivity through callbacks
@app.callback(
Output('Graph1', 'figure'),
[Input('Check1', 'value')])
def subset_graph(value):
dfs = df[value]
fig2 = px.line(dfs, x = dfs.index, y = dfs.columns, template = 'plotly_dark')
return fig2
app.run_server(mode='inline', port = 9099)
Upvotes: 2