-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Bug] Make single dynamic dropdown clearable (#915)
- Loading branch information
Showing
5 changed files
with
274 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<!-- | ||
A new scriv changelog fragment. | ||
Uncomment the section that is right (remove the HTML comment wrapper). | ||
--> | ||
|
||
<!-- | ||
### Highlights ✨ | ||
- A bullet item for the Highlights ✨ category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> | ||
<!-- | ||
### Removed | ||
- A bullet item for the Removed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> | ||
<!-- | ||
### Added | ||
- A bullet item for the Added category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> | ||
<!-- | ||
### Changed | ||
- A bullet item for the Changed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> | ||
<!-- | ||
### Deprecated | ||
- A bullet item for the Deprecated category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> | ||
|
||
### Fixed | ||
|
||
- Ensure the single-select dropdown value can be cleared when used as a dynamic filter. ([#915](https://github.com/mckinsey/vizro/pull/915)) | ||
|
||
<!-- | ||
### Security | ||
- A bullet item for the Security category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
--> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,248 @@ | ||
from typing import List, Literal | ||
"""Dev app to try things out.""" | ||
|
||
from dash import html | ||
import time | ||
import yaml | ||
|
||
import dash | ||
import pandas as pd | ||
from flask_caching import Cache | ||
|
||
import vizro.models as vm | ||
import vizro.plotly.express as px | ||
from vizro import Vizro | ||
from vizro.models.types import ControlType | ||
from vizro.managers import data_manager | ||
from functools import partial | ||
|
||
print("INITIALIZING") | ||
|
||
df_gapminder = px.data.gapminder() | ||
SPECIES_COLORS = {"setosa": "#00b4ff", "versicolor": "#ff9222", "virginica": "#3949ab"} | ||
BAR_CHART_CONF = dict(x="species", color="species", color_discrete_map=SPECIES_COLORS) | ||
SCATTER_CHART_CONF = dict(x="sepal_length", y="petal_length", color="species", color_discrete_map=SPECIES_COLORS) | ||
|
||
|
||
class ControlGroup(vm.VizroBaseModel): | ||
"""Container to group controls.""" | ||
def load_from_file(filter_column=None, parametrized_species=None): | ||
# Load the full iris dataset | ||
df = px.data.iris() | ||
df["date_column"] = pd.date_range(start=pd.to_datetime("2024-01-01"), periods=len(df), freq="D") | ||
|
||
type: Literal["control_group"] = "control_group" | ||
title: str | ||
controls: List[ControlType] = [] | ||
with open("data.yaml", "r") as file: | ||
data = { | ||
"setosa": 0, | ||
"versicolor": 0, | ||
"virginica": 0, | ||
"min": 0, | ||
"max": 10, | ||
"date_min": "2024-01-01", | ||
"date_max": "2024-05-29", | ||
} | ||
data.update(yaml.safe_load(file) or {}) | ||
|
||
def build(self): | ||
return html.Div( | ||
[html.H4(self.title), html.Hr()] + [control.build() for control in self.controls], | ||
if filter_column == "species": | ||
df = pd.concat( | ||
objs=[ | ||
df[df[filter_column] == "setosa"].head(data["setosa"]), | ||
df[df[filter_column] == "versicolor"].head(data["versicolor"]), | ||
df[df[filter_column] == "virginica"].head(data["virginica"]), | ||
], | ||
ignore_index=True, | ||
) | ||
elif filter_column == "sepal_length": | ||
df = df[df[filter_column].between(data["min"], data["max"], inclusive="both")] | ||
elif filter_column == "date_column": | ||
date_min = pd.to_datetime(data["date_min"]) | ||
date_max = pd.to_datetime(data["date_max"]) | ||
df = df[df[filter_column].between(date_min, date_max, inclusive="both")] | ||
else: | ||
raise ValueError("Invalid filter_column") | ||
|
||
if parametrized_species: | ||
df = df[df["species"].isin(parametrized_species)] | ||
|
||
return df | ||
|
||
|
||
data_manager["load_from_file_species"] = partial(load_from_file, filter_column="species") | ||
data_manager["load_from_file_sepal_length"] = partial(load_from_file, filter_column="sepal_length") | ||
data_manager["load_from_file_date_column"] = partial(load_from_file, filter_column="date_column") | ||
|
||
|
||
# TODO-DEV: Turn on/off caching to see how it affects the app. | ||
# data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 10}) | ||
|
||
|
||
homepage = vm.Page( | ||
title="Homepage", | ||
components=[ | ||
vm.Card(text="This is the homepage."), | ||
], | ||
) | ||
|
||
page_1 = vm.Page( | ||
title="Dynamic vs Static filter", | ||
components=[ | ||
vm.Graph( | ||
id="p1-G-1", | ||
figure=px.bar(data_frame="load_from_file_species", **BAR_CHART_CONF), | ||
), | ||
vm.Graph( | ||
id="p1-G-2", | ||
figure=px.scatter(data_frame=px.data.iris(), **SCATTER_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
vm.Filter(id="p1-F-1", column="species", targets=["p1-G-1"], selector=vm.Dropdown(title="Dynamic filter")), | ||
vm.Filter(id="p1-F-2", column="species", targets=["p1-G-2"], selector=vm.Dropdown(title="Static filter")), | ||
vm.Parameter( | ||
targets=["p1-G-1.x", "p1-G-2.x"], | ||
selector=vm.RadioItems(options=["species", "sepal_width"], title="Simple X-axis parameter"), | ||
), | ||
], | ||
) | ||
|
||
|
||
page_2 = vm.Page( | ||
title="Categorical dynamic selectors", | ||
components=[ | ||
vm.Graph( | ||
id="p2-G-1", | ||
figure=px.bar(data_frame="load_from_file_species", **BAR_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
vm.Filter(id="p2-F-1", column="species", selector=vm.Dropdown()), | ||
vm.Filter(id="p2-F-2", column="species", selector=vm.Dropdown(multi=False)), | ||
vm.Filter(id="p2-F-3", column="species", selector=vm.Checklist()), | ||
vm.Filter(id="p2-F-4", column="species", selector=vm.RadioItems()), | ||
vm.Parameter( | ||
targets=["p2-G-1.x"], | ||
selector=vm.RadioItems( | ||
options=["species", "sepal_width"], value="species", title="Simple X-axis parameter" | ||
), | ||
), | ||
], | ||
) | ||
|
||
|
||
vm.Page.add_type("controls", ControlGroup) | ||
page_3 = vm.Page( | ||
title="Numerical dynamic selectors", | ||
components=[ | ||
vm.Graph( | ||
id="p3-G-1", | ||
figure=px.bar(data_frame="load_from_file_sepal_length", **BAR_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
vm.Filter(id="p3-F-1", column="sepal_length", selector=vm.Slider()), | ||
vm.Filter(id="p3-F-2", column="sepal_length", selector=vm.RangeSlider()), | ||
vm.Parameter( | ||
targets=["p3-G-1.x"], | ||
selector=vm.RadioItems( | ||
options=["species", "sepal_width"], value="species", title="Simple X-axis parameter" | ||
), | ||
), | ||
], | ||
) | ||
|
||
page_4 = vm.Page( | ||
title="[TO BE DONE IN THE FOLLOW UP PR] Temporal dynamic selectors", | ||
components=[ | ||
vm.Graph( | ||
id="p4-G-1", | ||
figure=px.bar(data_frame="load_from_file_date_column", **BAR_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
vm.Filter(id="p4-F-1", column="date_column", selector=vm.DatePicker(range=False)), | ||
vm.Filter(id="p4-F-2", column="date_column", selector=vm.DatePicker()), | ||
vm.Parameter( | ||
targets=["p4-G-1.x"], | ||
selector=vm.RadioItems( | ||
options=["species", "sepal_width"], value="species", title="Simple X-axis parameter" | ||
), | ||
), | ||
], | ||
) | ||
|
||
page1 = vm.Page( | ||
title="Relationship Analysis", | ||
page_5 = vm.Page( | ||
title="Parametrised dynamic selectors", | ||
components=[ | ||
vm.Graph(id="scatter", figure=px.scatter(df_gapminder, x="gdpPercap", y="lifeExp", size="pop")), | ||
vm.Graph( | ||
id="p5-G-1", | ||
figure=px.bar(data_frame="load_from_file_species", **BAR_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
ControlGroup( | ||
title="Group A", | ||
controls=[ | ||
vm.Parameter( | ||
id="this", | ||
targets=["scatter.x"], | ||
selector=vm.Dropdown( | ||
options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose x-axis" | ||
), | ||
), | ||
vm.Parameter( | ||
targets=["scatter.y"], | ||
selector=vm.Dropdown( | ||
options=["lifeExp", "gdpPercap", "pop"], multi=False, value="lifeExp", title="Choose y-axis" | ||
), | ||
), | ||
vm.Filter(id="p5-F-1", column="species", targets=["p5-G-1"], selector=vm.Checklist()), | ||
vm.Parameter( | ||
targets=[ | ||
"p5-G-1.data_frame.parametrized_species", | ||
# TODO: Uncomment the following target and see the magic :D | ||
# Is this the indicator that parameter.targets prop has to support 'target' definition without the '.'? | ||
# "p5-F-1.", | ||
], | ||
selector=vm.Dropdown( | ||
options=["setosa", "versicolor", "virginica"], multi=True, title="Parametrized species" | ||
), | ||
), | ||
ControlGroup( | ||
title="Group B", | ||
controls=[ | ||
vm.Parameter( | ||
targets=["scatter.size"], | ||
selector=vm.Dropdown( | ||
options=["lifeExp", "gdpPercap", "pop"], multi=False, value="pop", title="Choose bubble size" | ||
), | ||
) | ||
vm.Parameter( | ||
targets=[ | ||
"p5-G-1.x", | ||
# TODO: Uncomment the following target and see the magic :D | ||
# "p5-F-1.", | ||
], | ||
selector=vm.RadioItems( | ||
options=["species", "sepal_width"], value="species", title="Simple X-axis parameter" | ||
), | ||
), | ||
], | ||
) | ||
|
||
dashboard = vm.Dashboard(pages=[page1]) | ||
|
||
page_6 = vm.Page( | ||
title="Page to test things out", | ||
components=[ | ||
vm.Graph(id="graph_dynamic", figure=px.bar(data_frame="load_from_file_species", **BAR_CHART_CONF)), | ||
vm.Graph( | ||
id="graph_static", | ||
figure=px.scatter(data_frame=px.data.iris(), **SCATTER_CHART_CONF), | ||
), | ||
], | ||
controls=[ | ||
vm.Filter( | ||
id="filter_container_id", | ||
column="species", | ||
targets=["graph_dynamic"], | ||
# targets=["graph_static"], | ||
# selector=vm.Dropdown(id="filter_id"), | ||
# selector=vm.Dropdown(id="filter_id", value=["setosa"]), | ||
# selector=vm.Checklist(id="filter_id"), | ||
# selector=vm.Checklist(id="filter_id", value=["setosa"]), | ||
# TODO-BUG: vm.Dropdown(multi=False) Doesn't work if value is cleared. The persistence storage become | ||
# "null" and our placeholder component dmc.DateRangePicker can't process null value. It expects a value or | ||
# a list of values. | ||
# SOLUTION -> Create the "Universal Vizro placeholder component". | ||
# TEMPORARY SOLUTION -> set clearable=False for the dynamic Dropdown(multi=False) | ||
# selector=vm.Dropdown(id="filter_id", multi=False), | ||
# selector=vm.Dropdown(id="filter_id", multi=False, value="setosa"), | ||
# selector=vm.RadioItems(id="filter_id"), | ||
# selector=vm.RadioItems(id="filter_id", value="setosa"), | ||
# selector=vm.Slider(id="filter_id"), | ||
# selector=vm.Slider(id="filter_id", value=5), | ||
# selector=vm.RangeSlider(id="filter_id"), | ||
# selector=vm.RangeSlider(id="filter_id", value=[5, 7]), | ||
), | ||
vm.Parameter( | ||
targets=["graph_dynamic.x"], | ||
selector=vm.RadioItems(options=["species", "sepal_width"], title="Simple X-axis parameter"), | ||
), | ||
], | ||
) | ||
|
||
dashboard = vm.Dashboard(pages=[homepage, page_1, page_2, page_3, page_4, page_5, page_6]) | ||
|
||
if __name__ == "__main__": | ||
Vizro().build(dashboard).run() | ||
app = Vizro().build(dashboard) | ||
|
||
print("RUNNING\n") | ||
|
||
app.run(dev_tools_hot_reload=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters