From c5a4bd369ebd0942df7cc7012d0012ffd0d4eae2 Mon Sep 17 00:00:00 2001 From: nadijagraca <108531476+nadijagraca@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:26:35 +0100 Subject: [PATCH] [Feat] Enable temporal selector `DatePicker` (#309) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: huong-li-nguyen Co-authored-by: Li Nguyen <90609403+huong-li-nguyen@users.noreply.github.com> Co-authored-by: petar-qb --- ...132602_nadija_ratkusic_graca_datepicker.md | 46 +++ .../selectors/default_filter_selectors.png | Bin 0 -> 147991 bytes vizro-core/docs/pages/user-guides/filters.md | 6 +- .../docs/pages/user-guides/selectors.md | 123 ++++++- vizro-core/examples/_dev/app.py | 277 ++++++++------- vizro-core/examples/_dev/yaml_version/app.py | 11 + .../examples/_dev/yaml_version/dashboard.yaml | 33 +- vizro-core/examples/features/app.py | 19 +- .../examples/features/yaml_version/app.py | 7 +- .../features/yaml_version/dashboard.yaml | 28 +- vizro-core/pyproject.toml | 2 +- vizro-core/schemas/0.1.13.json | 77 +++++ vizro-core/schemas/0.1.14.dev0.json | 124 +++++++ vizro-core/snyk/requirements.txt | 2 +- vizro-core/src/vizro/models/__init__.py | 7 +- .../vizro/models/_components/form/__init__.py | 3 +- .../models/_components/form/_form_utils.py | 27 +- .../models/_components/form/date_picker.py | 111 ++++++ .../models/_components/form/range_slider.py | 4 +- .../vizro/models/_components/form/slider.py | 4 +- .../src/vizro/models/_controls/filter.py | 101 ++++-- .../src/vizro/models/_controls/parameter.py | 16 +- vizro-core/src/vizro/models/types.py | 11 +- .../src/vizro/static/css/datepicker.css | 109 ++++++ .../src/vizro/static/css/token_names.css | 2 + vizro-core/src/vizro/static/css/variables.css | 6 + .../vizro/static/js/clientside_functions.js | 6 + .../src/vizro/static/js/models/date_picker.js | 19 ++ vizro-core/tests/unit/vizro/conftest.py | 12 +- .../_components/form/test_date_picker.py | 142 ++++++++ .../_components/form/test_radio_items.py | 2 +- .../_components/form/test_range_slider.py | 2 +- .../models/_components/form/test_slider.py | 2 +- .../unit/vizro/models/_controls/conftest.py | 14 +- .../vizro/models/_controls/test_filter.py | 317 +++++++++++++++--- .../vizro/models/_controls/test_parameter.py | 40 +-- 36 files changed, 1419 insertions(+), 293 deletions(-) create mode 100644 vizro-core/changelog.d/20240219_132602_nadija_ratkusic_graca_datepicker.md create mode 100644 vizro-core/docs/assets/user_guides/selectors/default_filter_selectors.png create mode 100644 vizro-core/src/vizro/models/_components/form/date_picker.py create mode 100644 vizro-core/src/vizro/static/css/datepicker.css create mode 100644 vizro-core/src/vizro/static/js/models/date_picker.js create mode 100644 vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py diff --git a/vizro-core/changelog.d/20240219_132602_nadija_ratkusic_graca_datepicker.md b/vizro-core/changelog.d/20240219_132602_nadija_ratkusic_graca_datepicker.md new file mode 100644 index 000000000..f61abf2a8 --- /dev/null +++ b/vizro-core/changelog.d/20240219_132602_nadija_ratkusic_graca_datepicker.md @@ -0,0 +1,46 @@ + + +### Highlights ✨ + +- Introduce `DatePicker` model as a new `Filter` and `Parameter` selector. Visit the [user guide on selectors](https://vizro.readthedocs.io/en/stable/pages/user-guides/selectors/) for more details. ([#309](https://github.com/mckinsey/vizro/pull/309)) + + + + + + + diff --git a/vizro-core/docs/assets/user_guides/selectors/default_filter_selectors.png b/vizro-core/docs/assets/user_guides/selectors/default_filter_selectors.png new file mode 100644 index 0000000000000000000000000000000000000000..26df3368b4a321c069c13cf562dc001a64d3e1c4 GIT binary patch literal 147991 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 doesn't work properly with numerical column_type + # because it's impossible to compare "number" with "date" +] +temporal_selectors = [ + # vm.RangeSlider(id="temporal_range_slider"), # -> dcc.RangeSlider doesn't work with temporal data + # vm.Slider(id="temporal_slider"), # -> dcc.Slider doesn't work with temporal data + vm.Dropdown(id="temporal_dropdown"), + vm.RadioItems(id="temporal_radio_items"), + vm.Checklist(id="temporal_checklist"), + vm.DatePicker(id="temporal_date_picker"), +] -selectors = vm.Page( - title="Page 1", +categorical_selectors = [ + # vm.RangeSlider(id="categorical_range_slider"), # -> dcc.RangeSlider doesn't work with categorical data + # vm.Slider(id="categorical_slider"), # -> dcc.Slider doesn't work with categorical data + vm.Dropdown(id="categorical_dropdown"), + vm.RadioItems(id="categorical_radio_items"), + vm.Checklist(id="categorical_checklist"), + # vm.DatePicker(id="categorical_date_picker") # -> dmc.DatePicker doesn't work with categorical data +] + +page = vm.Page( + title="My first page", components=[ - vm.Graph( - id="scatter_relation", - figure=px.scatter(data_frame=px.data.gapminder(), x="gdpPercap", y="lifeExp", size="pop"), - ), + vm.Graph(figure=px.line(date_data_frame, x="time", y="value")), + vm.DatePicker(min="2024-01-01", max="2026-01-01", range=False), ], controls=[ - vm.Filter( - column="continent", - selector=vm.Dropdown(title="Dropdown Label"), - ), - vm.Filter( - column="year", - selector=vm.RangeSlider(title="Range Slider Label", step=1, marks=None), - ), - vm.Filter( - column="year", - selector=vm.Slider(title="Slider Label"), - ), - vm.Filter( - column="year", - selector=vm.RangeSlider(title="Range Slider Label", step=10), - ), - vm.Filter( - column="year", - selector=vm.Slider(title="Slider Label", step=10), - ), - vm.Filter( - column="continent", - selector=vm.Checklist(title="Checklist Label"), - ), - vm.Filter( - column="continent", - selector=vm.RadioItems(title="Radio Items Label"), - ), - UserInput(title="Input - Text (single-line)", placeholder="Enter text here"), - TextArea(title="Input - Text (multi-line)", placeholder="Enter multi-line text here"), + # *[vm.Filter(column="value", selector=selector) for selector in numerical_selectors], + # *[vm.Filter(column="time", selector=selector) for selector in temporal_selectors], + # *[vm.Filter(column="type", selector=selector) for selector in categorical_selectors], + vm.Filter(column="type"), + vm.Filter(column="value"), + vm.Filter(column="time"), ], ) -form_components = vm.Page( - title="Page 2", +df_stocks_long = pd.melt( + px.data.stocks(datetimes=True), + id_vars="date", + value_vars=["GOOG", "AAPL", "AMZN", "FB", "NFLX", "MSFT"], + var_name="stocks", + value_name="value", +) + +df_stocks_long["value"] = df_stocks_long["value"].round(3) + +page_2 = vm.Page( + title="My second page", components=[ - vm.Container( - id="container-id", - title="Form", - components=[ - UserInput(title="Input - Text (single-line)", placeholder="Enter text here"), - TextArea(title="Input - Text (multi-line)", placeholder="Enter multi-line text here"), - vm.Dropdown(title="Dropdown - Single", options=["Option 1", "Option 2", "Option 3"], multi=False), - vm.Dropdown(title="Dropdown - Multi", options=["Option 1", "Option 2", "Option 3"], multi=True), - vm.Checklist(title="Checklist", options=["Option 1", "Option 2", "Option 3"]), - vm.RadioItems(title="Radio Items", options=["Option 1", "Option 2", "Option 3"]), - vm.Slider(title="Slider without marks", min=0, max=10), - vm.Slider(title="Slider with marks", min=0, max=10, step=1), - vm.RangeSlider(title="Range Slider without marks", min=0, max=10), - vm.RangeSlider(title="Range Slider with marks", min=0, max=10, step=1), - vm.Button(), - ], - ), + vm.Graph(figure=px.line(df_stocks_long, x="date", y="value", color="stocks")), + ], + controls=[ + vm.Filter(column="stocks"), + vm.Filter(column="value"), + vm.Filter(column="date"), ], ) -slider_marks = vm.Page( - title="Page 3", - layout=vm.Layout(grid=[[0, 1]], col_gap="100px"), - components=[ - vm.Container( - id="container-id-2", - title="Range Slider", - components=[ - vm.RangeSlider(title="Range Slider | step = None and marks = {}", min=0, max=10), - vm.RangeSlider( - title="Range Slider | step = None and marks = {''}", - min=0, - max=10, - step=None, - marks={0: "0", 5: "5", 10: "10"}, - ), - vm.RangeSlider( - title="Range Slider | step = None and marks = None", min=0, max=10, step=None, marks=None - ), - vm.RangeSlider(title="Range Slider | step = 1 and marks = {}", min=0, max=10, step=1), - vm.RangeSlider( - title="Range Slider | step = 1 and marks = {''}", - min=0, - max=10, - step=1, - marks={0: "0", 5: "5", 10: "10"}, - ), - vm.RangeSlider(title="Range Slider | step = 1 and marks = None", min=0, max=10, step=1, marks=None), - ], - ), - vm.Container( - id="container-id-3", - title="Slider", - components=[ - vm.Slider(title="Slider | step = None and marks = {}", min=0, max=10), - vm.Slider( - title="Slider | step = None and marks = {''}", - min=0, - max=10, - step=None, - marks={0: "0", 5: "5", 10: "10"}, - ), - vm.Slider(title="Slider | step = None and marks = None", min=0, max=10, step=None, marks=None), - vm.Slider(title="Slider | step = 1 and marks = {}", min=0, max=10, step=1), - vm.Slider( - title="Slider | step = 1 and marks = {''}", - min=0, - max=10, - step=1, - marks={0: "0", 5: "5", 10: "10"}, +# CUSTOM SELECTORS + + +class NewDropdown(VizroBaseModel): + """Categorical single/multi-selector `Dropdown` to be provided to `Filter`.""" + + type: Literal["new-dropdown"] = "new-dropdown" + options: Optional[OptionsType] = Field(None, description="Possible options the user can select from") + value: Optional[Union[SingleValueType, MultiValueType]] = Field( + None, description="Options that are selected by default" + ) + multi: bool = Field(True, description="Whether to allow selection of multiple values") + actions: List[Action] = [] # noqa: RUF012 + title: Optional[str] = Field(None, description="Title to be displayed") + + # Component properties for actions and interactions + _input_property: str = PrivateAttr("value") + + set_actions = _action_validator_factory("value") + + @_log_call + def build(self): + """Custom build method.""" + return html.Div( + [ + html.P(self.title) if self.title else None, + dcc.Dropdown( + id=self.id, + options=self.options, + value=self.value or self.options[0], + multi=self.multi, + persistence=True, + clearable=False, ), - vm.Slider(title="Slider | step = 1 and marks = None", min=0, max=10, step=1, marks=None), ], + className="input-container", + ) + + +class RangeSliderNonCross(vm.RangeSlider): + """Custom numeric multi-selector `RangeSliderNonCross` to be provided to `Filter`.""" + + type: Literal["range_slider_non_cross"] = "range_slider_non_cross" + + def build(self): + """Custom build method.""" + range_slider_build_obj = super().build() + range_slider_build_obj[self.id].allowCross = False + return range_slider_build_obj + + +# Important: Add new components to expected type - here the selector of the parent components +vm.Filter.add_type("selector", NewDropdown) +vm.Parameter.add_type("selector", NewDropdown) + + +# Important: Add new components to expected type - here the selector of the parent components +vm.Filter.add_type("selector", RangeSliderNonCross) +vm.Parameter.add_type("selector", RangeSliderNonCross) + + +page_3 = vm.Page( + title="Custom filer selectors", + components=[ + vm.Graph(figure=px.line(date_data_frame, x="time", y="value")), + ], + controls=[ + vm.Filter(column="type", selector=NewDropdown(options=["A", "B", "C"])), + vm.Filter(column="value", selector=NewDropdown(options=[1, 2, 3, 4, 5])), + vm.Filter( + column="time", + selector=NewDropdown( + options=[datetime.datetime(2024, 1, 1) + datetime.timedelta(days=i) for i in range(31)] + ), ), + vm.Filter(column="value", selector=RangeSliderNonCross(id="new_range_slider")), ], ) -dashboard = vm.Dashboard( - pages=[selectors, form_components, slider_marks], - navigation=vm.Navigation(nav_selector=vm.NavBar(pages=["Page 3", "Page 2", "Page 1"])), -) +dashboard = vm.Dashboard(pages=[page, page_2, page_3]) if __name__ == "__main__": Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_dev/yaml_version/app.py b/vizro-core/examples/_dev/yaml_version/app.py index dcddb5d46..8da1c9697 100644 --- a/vizro-core/examples/_dev/yaml_version/app.py +++ b/vizro-core/examples/_dev/yaml_version/app.py @@ -2,6 +2,7 @@ from pathlib import Path +import pandas as pd import vizro.plotly.express as px import yaml from vizro import Vizro @@ -12,6 +13,16 @@ data_manager["gapminder"] = px.data.gapminder data_manager["gapminder_2007"] = px.data.gapminder().query("year == 2007") + +df_stocks_long = pd.melt( + px.data.stocks(datetimes=True), + id_vars="date", + value_vars=["GOOG", "AAPL", "AMZN", "FB", "NFLX", "MSFT"], + var_name="stocks", + value_name="value", +) +data_manager["df_stocks_long"] = df_stocks_long + dashboard = yaml.safe_load(Path("dashboard.yaml").read_text(encoding="utf-8")) dashboard = Dashboard(**dashboard) diff --git a/vizro-core/examples/_dev/yaml_version/dashboard.yaml b/vizro-core/examples/_dev/yaml_version/dashboard.yaml index 666ba4118..053cf571c 100644 --- a/vizro-core/examples/_dev/yaml_version/dashboard.yaml +++ b/vizro-core/examples/_dev/yaml_version/dashboard.yaml @@ -2,25 +2,18 @@ # See yaml_version example pages: - components: - - type: ag_grid - figure: - _target_: dash_ag_grid - data_frame: gapminder_2007 - actions: - - function: - _target_: filter_interaction - targets: - - scatter_relation_2007 - - type: graph - id: scatter_relation_2007 - figure: - _target_: scatter - data_frame: gapminder_2007 - color: continent - x: gdpPercap - y: lifeExp - size: pop + - figure: + _target_: line + data_frame: df_stocks_long + x: date + y: value + color: stocks + type: graph controls: - - column: continent + - column: stocks type: filter - title: Filter interaction + - column: value + type: filter + - column: date + type: filter + title: My first page diff --git a/vizro-core/examples/features/app.py b/vizro-core/examples/features/app.py index 793ade314..ba7db671a 100644 --- a/vizro-core/examples/features/app.py +++ b/vizro-core/examples/features/app.py @@ -14,8 +14,8 @@ from vizro.tables import dash_ag_grid, dash_data_table iris = px.data.iris() -gapminder = px.data.gapminder() tips = px.data.tips() +stocks = px.data.stocks(datetimes=True) gapminder_2007 = px.data.gapminder().query("year == 2007") waterfall_df = pd.DataFrame( { @@ -57,6 +57,7 @@ * RadioItems * RangeSlider * Slider + * DatePicker """, href="/filters", ), @@ -353,7 +354,7 @@ selectors = vm.Page( title="Selectors", - layout=vm.Layout(grid=[[0], [1], [1], [1], [2], [2], [2]], row_min_height="170px", row_gap="24px"), + layout=vm.Layout(grid=[[0], [1], [1], [1], [2], [2], [2], [3], [3], [3]], row_min_height="170px", row_gap="24px"), components=[ vm.Card( text=""" @@ -365,19 +366,26 @@ * RadioItems (**categorical** single option selector only) * RangeSlider (**numerical** multi option selector only) * Slider (**numerical** single option selector only) + * DatePicker(**temporal** multi and single option selector) """ ), vm.Table( - id="table-gapminder", figure=dash_data_table(data_frame=gapminder, page_size=10), title="Gapminder Data" + id="table-gapminder", + figure=dash_data_table(data_frame=gapminder_2007, page_size=10), + title="Gapminder Data", ), vm.Table(id="table-tips", figure=dash_data_table(data_frame=tips, page_size=10), title="Tips Data"), + vm.Graph( + id="graph-stocks", + figure=px.line(stocks, x="date", y="GOOG", title="Stocks Data"), + ), ], controls=[ vm.Filter( targets=["table-gapminder"], - column="year", - selector=vm.RangeSlider(title="Range Slider (Gapminder - year)", step=1, marks=None), + column="lifeExp", + selector=vm.RangeSlider(title="Range Slider (Gapminder - lifeExp)", step=1, marks=None), ), vm.Filter( targets=["table-gapminder"], @@ -404,6 +412,7 @@ column="size", selector=vm.Slider(title="Slider (Tips - size)", step=1, value=2), ), + vm.Filter(targets=["graph-stocks"], column="date", selector=vm.DatePicker(title="Date Picker (Stocks - date)")), ], ) diff --git a/vizro-core/examples/features/yaml_version/app.py b/vizro-core/examples/features/yaml_version/app.py index 7f55e1cc3..78aee77d3 100644 --- a/vizro-core/examples/features/yaml_version/app.py +++ b/vizro-core/examples/features/yaml_version/app.py @@ -8,11 +8,10 @@ from vizro.managers import data_manager from vizro.models import Dashboard -data_manager["tips"] = px.data.tips() -gapminder = px.data.gapminder() -data_manager["gapminder"] = gapminder -data_manager["gapminder_2007"] = gapminder.query("year == 2007") data_manager["iris"] = px.data.iris() +data_manager["tips"] = px.data.tips() +data_manager["stocks"] = px.data.stocks(datetimes=True) +data_manager["gapminder_2007"] = px.data.gapminder().query("year == 2007") dashboard = yaml.safe_load(Path("dashboard.yaml").read_text(encoding="utf-8")) dashboard = Dashboard(**dashboard) diff --git a/vizro-core/examples/features/yaml_version/dashboard.yaml b/vizro-core/examples/features/yaml_version/dashboard.yaml index 21123b885..e91af21b1 100644 --- a/vizro-core/examples/features/yaml_version/dashboard.yaml +++ b/vizro-core/examples/features/yaml_version/dashboard.yaml @@ -22,6 +22,7 @@ pages: * RadioItems * RangeSlider * Slider + * DatePicker href: /filters type: card - text: | @@ -367,10 +368,11 @@ pages: * RadioItems (**categorical** single option selector only) * RangeSlider (**numerical** multi option selector only) * Slider (**numerical** single option selector only) + * DatePicker(**temporal** multi and single option selector) type: card - figure: _target_: dash_data_table - data_frame: gapminder + data_frame: gapminder_2007 page_size: 10 id: table-gapminder title: Gapminder Data @@ -382,10 +384,20 @@ pages: id: table-tips title: Tips Data type: table + - figure: + _target_: line + data_frame: stocks + title: Stocks Data + x: date + y: GOOG + id: graph-stocks + type: graph controls: - - column: year + - column: lifeExp selector: - title: Range Slider (Gapminder - year) + marks: null + step: 1 + title: Range Slider (Gapminder - lifeExp) type: range_slider targets: - table-gapminder @@ -429,6 +441,13 @@ pages: targets: - table-tips type: filter + - column: date + selector: + title: Date Picker (Stocks - date) + type: date_picker + targets: + - graph-stocks + type: filter layout: row_min_height: 170px row_gap: 24px @@ -440,6 +459,9 @@ pages: - [2] - [2] - [2] + - [3] + - [3] + - [3] title: Selectors navigation: nav_selector: diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 5d1e246fc..b695b2093 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "dash-ag-grid>=31.0.0", "pandas", "pydantic>=1.10.13", # must be synced with pre-commit mypy hook manually - "dash_mantine_components", + "dash_mantine_components<0.13.0", # 0.13.0 is not compatible with 0.12 "numpy>=1.22.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-2321970 "setuptools>=65.5.1", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-SETUPTOOLS-3180412 "werkzeug>=3.0.1" # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-WERKZEUG-6035177 diff --git a/vizro-core/schemas/0.1.13.json b/vizro-core/schemas/0.1.13.json index 0f2ed6338..851242cdc 100644 --- a/vizro-core/schemas/0.1.13.json +++ b/vizro-core/schemas/0.1.13.json @@ -499,6 +499,75 @@ }, "additionalProperties": false }, + "DatePicker": { + "title": "DatePicker", + "description": "Temporal single/range option selector `DatePicker`.\n\nCan be provided to [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter].\nBased on the underlying [`dmc.DatePicker`](https://www.dash-mantine-components.com/components/datepicker) or\n[`dmc.DateRangePicker`](https://www.dash-mantine-components.com/components/datepicker#daterangepicker).\n\nArgs:\n type (Literal[\"date_picker\"]): Defaults to `\"date_picker\"`.\n min (Optional[date]): Start date for date picker. Defaults to `None`.\n max (Optional[date]): End date for date picker. Defaults to `None`.\n value (Union[List[date], date]): Default date/dates for date picker. Defaults to `None`.\n title (str): Title to be displayed. Defaults to `\"\"`.\n range (bool): Boolean flag for displaying range picker. Default to `True`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", + "type": "object", + "properties": { + "id": { + "title": "Id", + "description": "ID to identify model. Must be unique throughout the whole dashboard.When no ID is chosen, ID will be automatically generated.", + "default": "", + "type": "string" + }, + "type": { + "title": "Type", + "default": "date_picker", + "enum": ["date_picker"], + "type": "string" + }, + "min": { + "title": "Min", + "description": "Start date for date picker.", + "type": "string", + "format": "date" + }, + "max": { + "title": "Max", + "description": "End date for date picker.", + "type": "string", + "format": "date" + }, + "value": { + "title": "Value", + "description": "Default date for date picker", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + { + "type": "string", + "format": "date" + } + ] + }, + "title": { + "title": "Title", + "description": "Title to be displayed.", + "default": "", + "type": "string" + }, + "range": { + "title": "Range", + "description": "Boolean flag for displaying range picker.", + "default": true, + "type": "boolean" + }, + "actions": { + "title": "Actions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Action" + } + } + }, + "additionalProperties": false + }, "Dropdown": { "title": "Dropdown", "description": "Categorical single/multi-option selector `Dropdown`.\n\nCan be provided to [`Filter`][vizro.models.Filter] or\n[`Parameter`][vizro.models.Parameter]. Based on the underlying\n[`dcc.Dropdown`](https://dash.plotly.com/dash-core-components/dropdown).\n\nArgs:\n type (Literal[\"dropdown\"]): Defaults to `\"dropdown\"`.\n options (OptionsType): See [`OptionsType`][vizro.models.types.OptionsType]. Defaults to `[]`.\n value (Optional[Union[SingleValueType, MultiValueType]]): See\n [`SingleValueType`][vizro.models.types.SingleValueType] and\n [`MultiValueType`][vizro.models.types.MultiValueType]. Defaults to `None`.\n multi (bool): Whether to allow selection of multiple values. Defaults to `True`.\n title (str): Title to be displayed. Defaults to `\"\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", @@ -862,6 +931,7 @@ "propertyName": "type", "mapping": { "checklist": "#/definitions/Checklist", + "date_picker": "#/definitions/DatePicker", "dropdown": "#/definitions/Dropdown", "radio_items": "#/definitions/RadioItems", "range_slider": "#/definitions/RangeSlider", @@ -872,6 +942,9 @@ { "$ref": "#/definitions/Checklist" }, + { + "$ref": "#/definitions/DatePicker" + }, { "$ref": "#/definitions/Dropdown" }, @@ -922,6 +995,7 @@ "propertyName": "type", "mapping": { "checklist": "#/definitions/Checklist", + "date_picker": "#/definitions/DatePicker", "dropdown": "#/definitions/Dropdown", "radio_items": "#/definitions/RadioItems", "range_slider": "#/definitions/RangeSlider", @@ -932,6 +1006,9 @@ { "$ref": "#/definitions/Checklist" }, + { + "$ref": "#/definitions/DatePicker" + }, { "$ref": "#/definitions/Dropdown" }, diff --git a/vizro-core/schemas/0.1.14.dev0.json b/vizro-core/schemas/0.1.14.dev0.json index 0f2ed6338..b019142e0 100644 --- a/vizro-core/schemas/0.1.14.dev0.json +++ b/vizro-core/schemas/0.1.14.dev0.json @@ -405,6 +405,10 @@ }, { "type": "string" + }, + { + "type": "string", + "format": "date" } ] } @@ -451,6 +455,13 @@ "type": "string" } }, + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, { "type": "array", "items": { @@ -479,6 +490,13 @@ "items": { "type": "string" } + }, + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } } ] }, @@ -499,6 +517,75 @@ }, "additionalProperties": false }, + "DatePicker": { + "title": "DatePicker", + "description": "Temporal single/range option selector `DatePicker`.\n\nCan be provided to [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter].\nBased on the underlying [`dmc.DatePicker`](https://www.dash-mantine-components.com/components/datepicker) or\n[`dmc.DateRangePicker`](https://www.dash-mantine-components.com/components/datepicker#daterangepicker).\n\nArgs:\n type (Literal[\"date_picker\"]): Defaults to `\"date_picker\"`.\n min (Optional[date]): Start date for date picker. Defaults to `None`.\n max (Optional[date]): End date for date picker. Defaults to `None`.\n value (Union[List[date], date]): Default date/dates for date picker. Defaults to `None`.\n title (str): Title to be displayed. Defaults to `\"\"`.\n range (bool): Boolean flag for displaying range picker. Default to `True`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", + "type": "object", + "properties": { + "id": { + "title": "Id", + "description": "ID to identify model. Must be unique throughout the whole dashboard.When no ID is chosen, ID will be automatically generated.", + "default": "", + "type": "string" + }, + "type": { + "title": "Type", + "default": "date_picker", + "enum": ["date_picker"], + "type": "string" + }, + "min": { + "title": "Min", + "description": "Start date for date picker.", + "type": "string", + "format": "date" + }, + "max": { + "title": "Max", + "description": "End date for date picker.", + "type": "string", + "format": "date" + }, + "value": { + "title": "Value", + "description": "Default date for date picker", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + { + "type": "string", + "format": "date" + } + ] + }, + "title": { + "title": "Title", + "description": "Title to be displayed.", + "default": "", + "type": "string" + }, + "range": { + "title": "Range", + "description": "Boolean flag for displaying range picker.", + "default": true, + "type": "boolean" + }, + "actions": { + "title": "Actions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Action" + } + } + }, + "additionalProperties": false + }, "Dropdown": { "title": "Dropdown", "description": "Categorical single/multi-option selector `Dropdown`.\n\nCan be provided to [`Filter`][vizro.models.Filter] or\n[`Parameter`][vizro.models.Parameter]. Based on the underlying\n[`dcc.Dropdown`](https://dash.plotly.com/dash-core-components/dropdown).\n\nArgs:\n type (Literal[\"dropdown\"]): Defaults to `\"dropdown\"`.\n options (OptionsType): See [`OptionsType`][vizro.models.types.OptionsType]. Defaults to `[]`.\n value (Optional[Union[SingleValueType, MultiValueType]]): See\n [`SingleValueType`][vizro.models.types.SingleValueType] and\n [`MultiValueType`][vizro.models.types.MultiValueType]. Defaults to `None`.\n multi (bool): Whether to allow selection of multiple values. Defaults to `True`.\n title (str): Title to be displayed. Defaults to `\"\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", @@ -538,6 +625,13 @@ "type": "string" } }, + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, { "type": "array", "items": { @@ -558,6 +652,10 @@ { "type": "string" }, + { + "type": "string", + "format": "date" + }, { "type": "array", "items": { @@ -575,6 +673,13 @@ "items": { "type": "string" } + }, + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } } ] }, @@ -640,6 +745,13 @@ "type": "string" } }, + { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, { "type": "array", "items": { @@ -659,6 +771,10 @@ }, { "type": "string" + }, + { + "type": "string", + "format": "date" } ] }, @@ -862,6 +978,7 @@ "propertyName": "type", "mapping": { "checklist": "#/definitions/Checklist", + "date_picker": "#/definitions/DatePicker", "dropdown": "#/definitions/Dropdown", "radio_items": "#/definitions/RadioItems", "range_slider": "#/definitions/RangeSlider", @@ -872,6 +989,9 @@ { "$ref": "#/definitions/Checklist" }, + { + "$ref": "#/definitions/DatePicker" + }, { "$ref": "#/definitions/Dropdown" }, @@ -922,6 +1042,7 @@ "propertyName": "type", "mapping": { "checklist": "#/definitions/Checklist", + "date_picker": "#/definitions/DatePicker", "dropdown": "#/definitions/Dropdown", "radio_items": "#/definitions/RadioItems", "range_slider": "#/definitions/RangeSlider", @@ -932,6 +1053,9 @@ { "$ref": "#/definitions/Checklist" }, + { + "$ref": "#/definitions/DatePicker" + }, { "$ref": "#/definitions/Dropdown" }, diff --git a/vizro-core/snyk/requirements.txt b/vizro-core/snyk/requirements.txt index 20dab3196..2da9cb17c 100644 --- a/vizro-core/snyk/requirements.txt +++ b/vizro-core/snyk/requirements.txt @@ -3,7 +3,7 @@ dash_bootstrap_components dash-ag-grid>=31.0.0 pandas pydantic>=1.10.13 -dash_mantine_components +dash_mantine_components<0.13.0 numpy>=1.22.2 setuptools>=65.5.1 werkzeug>=3.0.1 diff --git a/vizro-core/src/vizro/models/__init__.py b/vizro-core/src/vizro/models/__init__.py index 981f91c37..ac73d54ee 100644 --- a/vizro-core/src/vizro/models/__init__.py +++ b/vizro-core/src/vizro/models/__init__.py @@ -1,8 +1,9 @@ # Keep this import at the top to avoid circular imports since it's used in every model. from ._base import VizroBaseModel # noqa: I001 from ._action import Action -from ._components import Card, Container, Graph, AgGrid, Table, Tabs -from ._components.form import Button, Checklist, Dropdown, RadioItems, RangeSlider, Slider +from ._components import Card, Container, Graph, Table, Tabs +from ._components import AgGrid +from ._components.form import Button, Checklist, DatePicker, Dropdown, RadioItems, RangeSlider, Slider from ._controls import Filter, Parameter from ._navigation.accordion import Accordion from ._navigation.navigation import Navigation @@ -32,7 +33,6 @@ Dashboard.update_forward_refs(Page=Page, Navigation=Navigation) NavBar.update_forward_refs(NavLink=NavLink) NavLink.update_forward_refs(Accordion=Accordion) - # Please keep alphabetically ordered __all__ = [ "Accordion", @@ -43,6 +43,7 @@ "Container", "Checklist", "Dashboard", + "DatePicker", "Dropdown", "Filter", "Graph", diff --git a/vizro-core/src/vizro/models/_components/form/__init__.py b/vizro-core/src/vizro/models/_components/form/__init__.py index a2a9f964e..6d3e5b10b 100644 --- a/vizro-core/src/vizro/models/_components/form/__init__.py +++ b/vizro-core/src/vizro/models/_components/form/__init__.py @@ -1,9 +1,10 @@ from vizro.models._components.button import Button from vizro.models._components.form.checklist import Checklist +from vizro.models._components.form.date_picker import DatePicker from vizro.models._components.form.dropdown import Dropdown from vizro.models._components.form.radio_items import RadioItems from vizro.models._components.form.range_slider import RangeSlider from vizro.models._components.form.slider import Slider # Please keep alphabetically ordered -__all__ = ["Button", "Checklist", "Dropdown", "RadioItems", "RangeSlider", "Slider"] +__all__ = ["Button", "Checklist", "DatePicker", "Dropdown", "RadioItems", "RangeSlider", "Slider"] diff --git a/vizro-core/src/vizro/models/_components/form/_form_utils.py b/vizro-core/src/vizro/models/_components/form/_form_utils.py index 594152aa9..c216a5e38 100644 --- a/vizro-core/src/vizro/models/_components/form/_form_utils.py +++ b/vizro-core/src/vizro/models/_components/form/_form_utils.py @@ -1,5 +1,6 @@ """Helper functions for models inside form folder.""" +from datetime import date from typing import Union from vizro._constants import ALL_OPTION @@ -60,21 +61,26 @@ def validate_value(cls, value, values): def validate_max(cls, max, values): - """Reusable validator for the "max" argument for sliders.""" + """Validates that the `max` is not below the `min` for a range-based input.""" if max is None: return max if values["min"] is not None and max < values["min"]: - raise ValueError("Maximum value of slider is required to be larger than minimum value.") + raise ValueError("Maximum value of selector is required to be larger than minimum value.") return max -def validate_slider_value(cls, value, values): - """Reusable validator for the "value" argument for sliders.""" +def validate_range_value(cls, value, values): + """Validates a value or range of values to ensure they lie within specified bounds (min/max).""" + EXPECTED_VALUE_LENGTH = 2 if value is None: return value - lvalue, hvalue = (value[0], value[1]) if isinstance(value, list) else (value, value) + lvalue, hvalue = ( + (value[0], value[1]) + if isinstance(value, list) and len(value) == EXPECTED_VALUE_LENGTH + else (value[0], value[0]) if isinstance(value, list) and len(value) == 1 else (value, value) + ) if (values["min"] is not None and not lvalue >= values["min"]) or ( values["max"] is not None and not hvalue <= values["max"] @@ -100,3 +106,14 @@ def set_default_marks(cls, marks, values): if not marks and values.get("step") is None: marks = None return marks + + +def validate_date_picker_range(cls, range, values): + # + if range and values.get("value") and (isinstance(values["value"], (date, str)) or len(values["value"]) == 1): + raise ValueError("Please set range=False if providing single date value.") + + if not range and isinstance(values.get("value"), list): + raise ValueError("Please set range=True if providing list of date values.") + + return range diff --git a/vizro-core/src/vizro/models/_components/form/date_picker.py b/vizro-core/src/vizro/models/_components/form/date_picker.py new file mode 100644 index 000000000..bb70e9c47 --- /dev/null +++ b/vizro-core/src/vizro/models/_components/form/date_picker.py @@ -0,0 +1,111 @@ +from typing import List, Literal, Optional, Union + +import dash_mantine_components as dmc +from dash import ClientsideFunction, Input, Output, State, clientside_callback, dcc, html + +try: + from pydantic.v1 import Field, PrivateAttr, validator +except ImportError: # pragma: no cov + from pydantic import Field, PrivateAttr, validator + + +import datetime +from datetime import date + +from vizro.models import Action, VizroBaseModel +from vizro.models._action._actions_chain import _action_validator_factory +from vizro.models._components.form._form_utils import validate_date_picker_range, validate_max, validate_range_value + + +class DatePicker(VizroBaseModel): + """Temporal single/range option selector `DatePicker`. + + Can be provided to [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter]. + Based on the underlying [`dmc.DatePicker`](https://www.dash-mantine-components.com/components/datepicker) or + [`dmc.DateRangePicker`](https://www.dash-mantine-components.com/components/datepicker#daterangepicker). + + Args: + type (Literal["date_picker"]): Defaults to `"date_picker"`. + min (Optional[date]): Start date for date picker. Defaults to `None`. + max (Optional[date]): End date for date picker. Defaults to `None`. + value (Union[List[date], date]): Default date/dates for date picker. Defaults to `None`. + title (str): Title to be displayed. Defaults to `""`. + range (bool): Boolean flag for displaying range picker. Default to `True`. + actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. + + """ + + type: Literal["date_picker"] = "date_picker" + min: Optional[date] = Field(None, description="Start date for date picker.") + max: Optional[date] = Field(None, description="End date for date picker.") + value: Optional[Union[List[date], date]] = Field(None, description="Default date for date picker") + title: str = Field("", description="Title to be displayed.") + range: bool = Field(True, description="Boolean flag for displaying range picker.") + actions: List[Action] = [] + + _input_property: str = PrivateAttr("value") + _set_actions = _action_validator_factory("value") + + # Re-used validators + _validate_value = validator("value", allow_reuse=True)(validate_range_value) + _validate_max = validator("max", allow_reuse=True)(validate_max) + _validate_range = validator("range", allow_reuse=True, always=True)(validate_date_picker_range) + + def build(self): + init_value = self.value or ([self.min, self.max] if self.range else self.min) # type: ignore[list-item] + date_range_picker_kwargs = {"allowSingleDateInRange": True} if self.range else {} + + output = [ + Output(self.id, "value"), + Output(f"{self.id}_input_store", "data"), + ] + inputs = [ + Input(self.id, "value"), + State(f"{self.id}_input_store", "data"), + ] + + clientside_callback( + ClientsideFunction(namespace="clientside", function_name="update_date_picker_values"), + output=output, + inputs=inputs, + ) + # clientside callback is required as a workaround when the date-picker is overflowing its parent container + # if there is not enough space. Caused by another workaround for this issue: + # https://github.com/snehilvj/dash-mantine-components/issues/219 + clientside_callback( + ClientsideFunction(namespace="clientside", function_name="update_date_picker_position"), + output=Output(self.id, "dropdownPosition"), + inputs=Input(self.id, "n_clicks"), + ) + + date_picker_class = dmc.DateRangePicker if self.range else dmc.DatePicker + + # dropdownPosition must be set to bottom-start as a workaround for issue: + # https://github.com/snehilvj/dash-mantine-components/issues/219 + # clearable must be set to False as a workaround for issue: + # https://github.com/snehilvj/dash-mantine-components/issues/212 + # maxDate must be increased by one day, and later on disabledDates must be set as maxDate + 1 day + # as a workaround for issue: https://github.com/snehilvj/dash-mantine-components/issues/230 + date_picker = date_picker_class( + id=self.id, + minDate=self.min, + value=init_value, + maxDate=self.max + datetime.timedelta(days=1) if self.max else None, + persistence=True, + persistence_type="session", + dropdownPosition="bottom-start", + clearable=False, + disabledDates=self.max + datetime.timedelta(days=1) if self.max else None, + className="datepicker", + **date_range_picker_kwargs, + ) + + return html.Div( + [ + html.Label(self.title, htmlFor=self.id) if self.title else None, + date_picker, + dcc.Store(id=f"{self.id}_input_store", storage_type="session", data=init_value), + ], + className="selector_container", + id=f"{self.id}_outer", + ) diff --git a/vizro-core/src/vizro/models/_components/form/range_slider.py b/vizro-core/src/vizro/models/_components/form/range_slider.py index 50ec89a6c..b651fb074 100644 --- a/vizro-core/src/vizro/models/_components/form/range_slider.py +++ b/vizro-core/src/vizro/models/_components/form/range_slider.py @@ -12,7 +12,7 @@ from vizro.models._components.form._form_utils import ( set_default_marks, validate_max, - validate_slider_value, + validate_range_value, validate_step, ) from vizro.models._models_utils import _log_call @@ -53,7 +53,7 @@ class RangeSlider(VizroBaseModel): # Re-used validators _validate_max = validator("max", allow_reuse=True)(validate_max) - _validate_value = validator("value", allow_reuse=True)(validate_slider_value) + _validate_value = validator("value", allow_reuse=True)(validate_range_value) _validate_step = validator("step", allow_reuse=True)(validate_step) _set_default_marks = validator("marks", allow_reuse=True, always=True)(set_default_marks) _set_actions = _action_validator_factory("value") diff --git a/vizro-core/src/vizro/models/_components/form/slider.py b/vizro-core/src/vizro/models/_components/form/slider.py index 165660165..3675cb663 100644 --- a/vizro-core/src/vizro/models/_components/form/slider.py +++ b/vizro-core/src/vizro/models/_components/form/slider.py @@ -12,7 +12,7 @@ from vizro.models._components.form._form_utils import ( set_default_marks, validate_max, - validate_slider_value, + validate_range_value, validate_step, ) from vizro.models._models_utils import _log_call @@ -51,7 +51,7 @@ class Slider(VizroBaseModel): # Re-used validators _validate_max = validator("max", allow_reuse=True)(validate_max) - _validate_value = validator("value", allow_reuse=True)(validate_slider_value) + _validate_value = validator("value", allow_reuse=True)(validate_range_value) _validate_step = validator("step", allow_reuse=True)(validate_step) _set_default_marks = validator("marks", allow_reuse=True, always=True)(set_default_marks) _set_actions = _action_validator_factory("value") diff --git a/vizro-core/src/vizro/models/_controls/filter.py b/vizro-core/src/vizro/models/_controls/filter.py index 911cc82bb..26d808f5b 100644 --- a/vizro-core/src/vizro/models/_controls/filter.py +++ b/vizro-core/src/vizro/models/_controls/filter.py @@ -1,9 +1,9 @@ from __future__ import annotations -from typing import List, Literal +from typing import List, Literal, Union import pandas as pd -from pandas.api.types import is_numeric_dtype +from pandas.api.types import is_datetime64_any_dtype, is_numeric_dtype try: from pydantic.v1 import Field, PrivateAttr, validator @@ -15,23 +15,50 @@ from vizro.managers import data_manager, model_manager from vizro.managers._model_manager import ModelID from vizro.models import Action, VizroBaseModel -from vizro.models._components.form import Checklist, Dropdown, RadioItems, RangeSlider, Slider +from vizro.models._components.form import ( + Checklist, + DatePicker, + Dropdown, + RadioItems, + RangeSlider, + Slider, +) from vizro.models._models_utils import _log_call from vizro.models.types import MultiValueType, SelectorType -# TODO: Add temporal when relevant component is available -SELECTOR_DEFAULTS = {"numerical": RangeSlider, "categorical": Dropdown} - # Ideally we might define these as NumericalSelectorType = Union[RangeSlider, Slider] etc., but that will not work # with isinstance checks. -SELECTORS = {"numerical": (RangeSlider, Slider), "categorical": (Checklist, Dropdown, RadioItems)} - - -def _filter_between(series: pd.Series, value: List[float]) -> pd.Series: +# First entry in each tuple is the default selector for that column type. +SELECTORS = { + "numerical": (RangeSlider, Slider), + "categorical": (Dropdown, Checklist, RadioItems), + "temporal": (DatePicker,), +} + +# This disallowed selectors for each column type map is based on the discussion at the following link: +# See https://github.com/mckinsey/vizro/pull/319#discussion_r1524888171 +DISALLOWED_SELECTORS = { + "numerical": SELECTORS["temporal"], + "temporal": SELECTORS["numerical"], + "categorical": SELECTORS["numerical"] + SELECTORS["temporal"], +} + + +def _filter_between(series: pd.Series, value: Union[List[float], List[str]]) -> pd.Series: + if is_datetime64_any_dtype(series): + # Each value will always have time 00:00:00. In order for the filter to include all times during + # the end date value[1] we need to remove the time part of every value in series so that it's 00:00:00. + value = pd.to_datetime(value) + series = pd.to_datetime(series.dt.date) return series.between(value[0], value[1], inclusive="both") def _filter_isin(series: pd.Series, value: MultiValueType) -> pd.Series: + if is_datetime64_any_dtype(series): + # Value will always have time 00:00:00. In order for the filter to include all times during + # the end date value we need to remove the time part of every value in series so that it's 00:00:00. + value = pd.to_datetime(value) + series = pd.to_datetime(series.dt.date) return series.isin(value) @@ -58,7 +85,7 @@ class Filter(VizroBaseModel): "If none are given then target all components on the page that use `column`.", ) selector: SelectorType = None - _column_type: Literal["numerical", "categorical"] = PrivateAttr() + _column_type: Literal["numerical", "categorical", "temporal"] = PrivateAttr() @validator("targets", each_item=True) def check_target_present(cls, target): @@ -71,7 +98,8 @@ def pre_build(self): self._set_targets() self._set_column_type() self._set_selector() - self._set_slider_values() + self._validate_disallowed_selector() + self._set_numerical_and_temporal_selectors_values() self._set_categorical_selectors_options() self._set_actions() @@ -92,39 +120,56 @@ def _set_targets(self): def _set_column_type(self): data_frame = data_manager._get_component_data(self.targets[0]) - if isinstance(data_frame[self.column], pd.PeriodDtype) or is_numeric_dtype(data_frame[self.column]): + + if is_numeric_dtype(data_frame[self.column]): self._column_type = "numerical" + elif is_datetime64_any_dtype(data_frame[self.column]): + self._column_type = "temporal" else: self._column_type = "categorical" def _set_selector(self): - self.selector = self.selector or SELECTOR_DEFAULTS[self._column_type]() + self.selector = self.selector or SELECTORS[self._column_type][0]() self.selector.title = self.selector.title or self.column.title() - def _set_slider_values(self): - if isinstance(self.selector, SELECTORS["numerical"]): - if self._column_type != "numerical": - raise ValueError( - f"Chosen selector {self.selector.type} is not compatible " - f"with {self._column_type} column '{self.column}'." - ) + def _validate_disallowed_selector(self): + if isinstance(self.selector, DISALLOWED_SELECTORS.get(self._column_type, ())): + raise ValueError( + f"Chosen selector {self.selector.type} is not compatible " + f"with {self._column_type} column '{self.column}'. " + ) + + def _set_numerical_and_temporal_selectors_values(self): + # If the selector is a numerical or temporal selector, and the min and max values are not set, then set them + # N.B. All custom selectors inherit from numerical or temporal selector should also pass this check + if isinstance(self.selector, SELECTORS["numerical"] + SELECTORS["temporal"]): min_values = [] max_values = [] for target_id in self.targets: data_frame = data_manager._get_component_data(target_id) min_values.append(data_frame[self.column].min()) max_values.append(data_frame[self.column].max()) - if not is_numeric_dtype(pd.Series(min_values)) or not is_numeric_dtype(pd.Series(max_values)): + + if not ( + is_numeric_dtype(pd.Series(min_values)) + and is_numeric_dtype(pd.Series(max_values)) + or is_datetime64_any_dtype(pd.Series(min_values)) + and is_datetime64_any_dtype(pd.Series(max_values)) + ): raise ValueError( - f"Non-numeric values detected in the shared data column '{self.column}' for targeted charts. " - f"Please ensure that the data column contains the same data type across all targeted charts." + f"Inconsistent types detected in the shared data column '{self.column}' for targeted charts " + f"{self.targets}. Please ensure that the data column contains the same data type across all " + f"targeted charts." ) + if self.selector.min is None: self.selector.min = min(min_values) if self.selector.max is None: self.selector.max = max(max_values) def _set_categorical_selectors_options(self): + # If the selector is a categorical selector, and the options are not set, then set them + # N.B. All custom selectors inherit from categorical selector should also pass this check if isinstance(self.selector, SELECTORS["categorical"]) and not self.selector.options: options = set() for target_id in self.targets: @@ -135,7 +180,13 @@ def _set_categorical_selectors_options(self): def _set_actions(self): if not self.selector.actions: - filter_function = _filter_between if isinstance(self.selector, RangeSlider) else _filter_isin + if isinstance(self.selector, RangeSlider) or ( + isinstance(self.selector, DatePicker) and self.selector.range + ): + filter_function = _filter_between + else: + filter_function = _filter_isin + self.selector.actions = [ Action( function=_filter(filter_column=self.column, targets=self.targets, filter_function=filter_function), diff --git a/vizro-core/src/vizro/models/_controls/parameter.py b/vizro-core/src/vizro/models/_controls/parameter.py index 298553f9b..51a97b884 100644 --- a/vizro-core/src/vizro/models/_controls/parameter.py +++ b/vizro-core/src/vizro/models/_controls/parameter.py @@ -9,7 +9,7 @@ from vizro.actions import _parameter from vizro.managers import model_manager from vizro.models import Action, VizroBaseModel -from vizro.models._components.form import Checklist, Dropdown, RadioItems, RangeSlider, Slider +from vizro.models._components.form import Checklist, DatePicker, Dropdown, RadioItems, RangeSlider, Slider from vizro.models._models_utils import _log_call from vizro.models.types import SelectorType @@ -61,27 +61,27 @@ def check_duplicate_parameter_target(cls, targets): @_log_call def pre_build(self): - self._set_slider_values() - self._set_categorical_selectors_options() - self._set_selector() + self._check_numerical_and_temporal_selectors_values() + self._check_categorical_selectors_options() + self._set_selector_title() self._set_actions() @_log_call def build(self): return self.selector.build() - def _set_slider_values(self): - if isinstance(self.selector, (Slider, RangeSlider)): + def _check_numerical_and_temporal_selectors_values(self): + if isinstance(self.selector, (Slider, RangeSlider, DatePicker)): if self.selector.min is None or self.selector.max is None: raise TypeError( f"{self.selector.type} requires the arguments 'min' and 'max' when used within Parameter." ) - def _set_categorical_selectors_options(self): + def _check_categorical_selectors_options(self): if isinstance(self.selector, (Checklist, Dropdown, RadioItems)) and not self.selector.options: raise TypeError(f"{self.selector.type} requires the argument 'options' when used within Parameter.") - def _set_selector(self): + def _set_selector_title(self): if not self.selector.title: self.selector.title = ", ".join({target.rsplit(".")[-1] for target in self.targets}) diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index 1fd5ff446..a3e809666 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -5,6 +5,7 @@ import functools import inspect +from datetime import date from typing import Any, Dict, List, Literal, Protocol, Union, runtime_checkable try: @@ -329,9 +330,9 @@ def wrapped(*args, **kwargs): # Types used for selector values and options. Note the docstrings here are rendered on the API reference. -SingleValueType = Union[StrictBool, float, str] +SingleValueType = Union[StrictBool, float, str, date] """Permissible value types for single-value selectors. Values are displayed as default.""" -MultiValueType = Union[List[StrictBool], List[float], List[str]] +MultiValueType = Union[List[StrictBool], List[float], List[str], List[date]] """Permissible value types for multi-value selectors. Values are displayed as default.""" @@ -342,16 +343,16 @@ class OptionsDictType(TypedDict): value: SingleValueType -OptionsType = Union[List[StrictBool], List[float], List[str], List[OptionsDictType]] +OptionsType = Union[List[StrictBool], List[float], List[str], List[date], List[OptionsDictType]] """Permissible options types for selectors. Options are available choices for user to select from.""" # All the below types rely on models and so must use ForwardRef (i.e. "Checklist" rather than actual Checklist class). SelectorType = Annotated[ - Union["Checklist", "Dropdown", "RadioItems", "RangeSlider", "Slider"], + Union["Checklist", "DatePicker", "Dropdown", "RadioItems", "RangeSlider", "Slider"], Field(discriminator="type", description="Selectors to be used inside a control."), ] """Discriminated union. Type of selector to be used inside a control: [`Checklist`][vizro.models.Checklist], -[`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], +[`DatePicker`][vizro.models.DatePicker], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], [`RangeSlider`][vizro.models.RangeSlider] or [`Slider`][vizro.models.Slider].""" _FormComponentType = Annotated[ diff --git a/vizro-core/src/vizro/static/css/datepicker.css b/vizro-core/src/vizro/static/css/datepicker.css new file mode 100644 index 000000000..b2d4a4ebb --- /dev/null +++ b/vizro-core/src/vizro/static/css/datepicker.css @@ -0,0 +1,109 @@ +.datepicker .mantine-Input-wrapper { + font-family: unset; + height: var(--spacing-08); +} + +.datepicker .mantine-DateRangePicker-input, +.datepicker .mantine-DatePicker-input { + background-color: var(--field-enabled); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); + color: var(--text-secondary); + font-size: var(--text-size-02); + height: var(--spacing-08); + line-height: var(--spacing-04); + min-height: var(--spacing-08); + padding: 0 var(--spacing-02); +} + +.datepicker .mantine-DateRangePicker-input:hover, +.datepicker .mantine-DatePicker-input:hover { + color: var(--text-primary); +} + +.datepicker .mantine-DateRangePicker-dropdown, +.datepicker .mantine-DatePicker-dropdown { + background: var(--field-enabled); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-1); + padding: var(--spacing-04) 11px; /* 11px otherwise not aligned with controls */ +} + +.datepicker .mantine-UnstyledButton-root { + border-radius: 0; + color: var(--text-secondary); + font-family: unset; + font-weight: 400; +} + +.datepicker .mantine-UnstyledButton-root:hover { + background: var(--state-overlays-hover); + color: var(--text-primary); +} + +.datepicker .mantine-DateRangePicker-weekday, +.datepicker .mantine-DatePicker-weekday { + color: var(--text-secondary); + font-family: unset; + padding: var(--spacing-02); +} + +.datepicker .mantine-DateRangePicker-cell, +.datepicker .mantine-DatePicker-cell { + border: none; +} + +.datepicker .mantine-DateRangePicker-day, +.datepicker .mantine-DatePicker-day { + background: var(--field-enabled); + border-radius: 0; + color: var(--text-secondary); + font-family: unset; +} + +.datepicker .mantine-DateRangePicker-day[data-outside], +.datepicker .mantine-DateRangePicker-day:disabled, +.datepicker .mantine-DatePicker-day[data-outside], +.datepicker .mantine-DatePicker-day:disabled, +.datepicker .mantine-UnstyledButton-root:disabled { + color: var(--text-disabled); +} + +.datepicker .mantine-DateRangePicker-day:hover, +.datepicker .mantine-DateRangePicker-day:focus-visible, +.datepicker .mantine-DatePicker-day:hover, +.datepicker .mantine-DatePicker-day:focus-visible { + background: var(--state-overlays-hover); + border: none; + color: var(--text-primary); + outline: none; + text-decoration: none; +} + +.datepicker .mantine-DateRangePicker-day[data-in-range] { + background: var(--state-overlays-selected); + color: var(--text-primary); +} + +.datepicker .mantine-DateRangePicker-day[data-selected], +.datepicker .mantine-DateRangePicker-yearPickerControlActive, +.datepicker .mantine-DateRangePicker-monthPickerControlActive, +.datepicker .mantine-DatePicker-day[data-selected], +.datepicker .mantine-DatePicker-yearPickerControlActive, +.datepicker .mantine-DatePicker-monthPickerControlActive { + background: var(--state-overlays-selected-inverse); + color: var(--text-contrast-primary); + text-decoration: underline; +} + +.datepicker + .mantine-DateRangePicker-calendarHeader + .mantine-UnstyledButton-root:hover, +.datepicker + .mantine-DatePicker-calendarHeader + .mantine-UnstyledButton-root:hover { + background: transparent; + color: var(--text-primary); +} diff --git a/vizro-core/src/vizro/static/css/token_names.css b/vizro-core/src/vizro/static/css/token_names.css index 456c0b90f..589269228 100644 --- a/vizro-core/src/vizro/static/css/token_names.css +++ b/vizro-core/src/vizro/static/css/token_names.css @@ -78,10 +78,12 @@ --state-overlays-dark-mode-enable: rgba(255, 255, 255, 0); --state-overlays-dark-mode-hover: rgba(255, 255, 255, 0.04); --state-overlays-dark-mode-selected: rgba(255, 255, 255, 0.1); + --state-overlays-dark-mode-selected-inverse: #ffffff; --state-overlays-dark-mode-selected-hover: rgba(255, 255, 255, 0.16); --state-overlays-light-mode-active: rgba(20, 23, 33, 0.12); --state-overlays-light-mode-enable: rgba(20, 23, 33, 0); --state-overlays-light-mode-hover: rgba(20, 23, 33, 0.06); --state-overlays-light-mode-selected: rgba(20, 23, 33, 0.08); + --state-overlays-light-mode-selected-inverse: #141721; --state-overlays-light-mode-selected-hover: rgba(20, 23, 33, 0.24); } diff --git a/vizro-core/src/vizro/static/css/variables.css b/vizro-core/src/vizro/static/css/variables.css index 54436a051..8a8b519cd 100644 --- a/vizro-core/src/vizro/static/css/variables.css +++ b/vizro-core/src/vizro/static/css/variables.css @@ -64,6 +64,9 @@ --state-overlays-enable: var(--state-overlays-dark-mode-enable); --state-overlays-hover: var(--state-overlays-dark-mode-hover); --state-overlays-selected: var(--state-overlays-dark-mode-selected); + --state-overlays-selected-inverse: var( + --state-overlays-dark-mode-selected-inverse + ); --state-overlays-selected-hover: var( --state-overlays-dark-mode-selected-hover ); @@ -125,6 +128,9 @@ --state-overlays-enable: var(--state-overlays-light-mode-enable); --state-overlays-hover: var(--state-overlays-light-mode-hover); --state-overlays-selected: var(--state-overlays-light-mode-selected); + --state-overlays-selected-inverse: var( + --state-overlays-light-mode-selected-inverse + ); --state-overlays-selected-hover: var( --state-overlays-light-mode-selected-hover ); diff --git a/vizro-core/src/vizro/static/js/clientside_functions.js b/vizro-core/src/vizro/static/js/clientside_functions.js index b5bb8424b..8cc5c76d8 100644 --- a/vizro-core/src/vizro/static/js/clientside_functions.js +++ b/vizro-core/src/vizro/static/js/clientside_functions.js @@ -10,6 +10,10 @@ import { _gateway, _after_action_cycle_breaker, } from "./actions/build_action_loop_callbacks.js"; +import { + _update_date_picker_values, + _update_date_picker_position, +} from "./models/date_picker.js"; window.dash_clientside = Object.assign({}, window.dash_clientside, { clientside: { @@ -21,5 +25,7 @@ window.dash_clientside = Object.assign({}, window.dash_clientside, { after_action_cycle_breaker: _after_action_cycle_breaker, collapse_nav_panel: _collapse_nav_panel, update_ag_grid_theme: _update_ag_grid_theme, + update_date_picker_values: _update_date_picker_values, + update_date_picker_position: _update_date_picker_position, }, }); diff --git a/vizro-core/src/vizro/static/js/models/date_picker.js b/vizro-core/src/vizro/static/js/models/date_picker.js new file mode 100644 index 000000000..e1d524bc7 --- /dev/null +++ b/vizro-core/src/vizro/static/js/models/date_picker.js @@ -0,0 +1,19 @@ +export function _update_date_picker_values(value, input_store) { + if ( + value === null || + dash_clientside.callback_context.triggered.length === 0 + ) { + return [input_store, input_store]; + } + return [value, value]; +} + +export function _update_date_picker_position(clicks) { + var element_id = window.dash_clientside.callback_context.inputs_list[0]["id"]; + var element = document.getElementById(element_id); + var rect = element.getBoundingClientRect(); + var position = + rect.y + 360 > window.innerHeight ? "top-start" : "bottom-start"; + + return position; +} diff --git a/vizro-core/tests/unit/vizro/conftest.py b/vizro-core/tests/unit/vizro/conftest.py index 42faf520b..f1b66035f 100644 --- a/vizro-core/tests/unit/vizro/conftest.py +++ b/vizro-core/tests/unit/vizro/conftest.py @@ -10,7 +10,12 @@ @pytest.fixture def gapminder(): - return px.data.gapminder() + return px.data.gapminder(datetimes=True) + + +@pytest.fixture +def stocks(): + return px.data.stocks() @pytest.fixture @@ -56,6 +61,11 @@ def standard_go_chart(gapminder): return go.Figure(data=go.Scatter(x=gapminder["gdpPercap"], y=gapminder["lifeExp"], mode="markers")) +@pytest.fixture +def chart_with_temporal_data(stocks): + return go.Figure(data=go.Scatter(x=stocks["Date"], y=stocks["AAPL.High"], mode="markers")) + + @pytest.fixture() def page_1(): return vm.Page(title="Page 1", components=[vm.Button()]) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py b/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py new file mode 100644 index 000000000..eeae756e9 --- /dev/null +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py @@ -0,0 +1,142 @@ +"""Unit tests for DatePicker.""" + +from datetime import date, datetime + +import dash_mantine_components as dmc +import pytest +from asserts import assert_component_equal +from dash import dcc, html + +try: + from pydantic.v1 import ValidationError +except ImportError: # pragma: no cov + from pydantic import ValidationError + +import vizro.models as vm + + +class TestDatePickerInstantiation: + """Tests model instantiation.""" + + def test_create_datepicker_mandatory(self): + date_picker = vm.DatePicker() + + assert hasattr(date_picker, "id") + assert date_picker.type == "date_picker" + assert date_picker.min is None + assert date_picker.max is None + assert date_picker.value is None + assert date_picker.title == "" + assert date_picker.actions == [] + assert date_picker.range is True + + def test_create_datepicker_mandatory_and_optional(self): + date_picker = vm.DatePicker( + id="date-picker-id", min="2024-01-01", max="2024-12-31", value=["2024-03-01", "2024-04-01"], title="Title" + ) + + assert date_picker.id == "date-picker-id" + assert date_picker.type == "date_picker" + assert date_picker.min == date(2024, 1, 1) + assert date_picker.max == date(2024, 12, 31) + assert date_picker.value == [date(2024, 3, 1), date(2024, 4, 1)] + assert date_picker.title == "Title" + assert date_picker.actions == [] + assert date_picker.range is True + + @pytest.mark.parametrize("title", ["test", 1, 1.0, """## Test header""", ""]) + def test_valid_title(self, title): + date_picker = vm.DatePicker(title=title) + + assert date_picker.title == str(title) + + def test_set_action_via_validator(self, identity_action_function): + date_picker = vm.DatePicker(actions=[vm.Action(function=identity_action_function())]) + actions_chain = date_picker.actions[0] + + assert actions_chain.trigger.component_property == "value" + + @pytest.mark.parametrize("min, max", [("2024-01-01", None), (None, "2024-01-01"), ("2024-01-01", "2024-02-01")]) + def test_valid_min_max(self, min, max): + date_picker = vm.DatePicker(min=min, max=max) + + assert date_picker.min == (datetime.strptime(min, "%Y-%m-%d").date() if min else None) + assert date_picker.max == (datetime.strptime(max, "%Y-%m-%d").date() if max else None) + + def test_validate_max_invalid_min_greater_than_max(self): + with pytest.raises( + ValidationError, match="Maximum value of selector is required to be larger than minimum value." + ): + vm.DatePicker(min="2024-02-01", max="2024-01-01") + + def test_validate_max_invalid_date_format(self): + with pytest.raises(ValidationError, match="invalid date format"): + vm.DatePicker(min="50-50-50", max="50-50-50") + + def test_validate_range_true_datepicker_value_valid(self): + value = ["2024-01-01", "2024-02-01"] + date_picker = vm.DatePicker(range=True, value=value) + expected_value = [date(2024, 1, 1), date(2024, 2, 1)] + assert date_picker.value == expected_value + + def test_validate_range_false_datepicker_value_valid(self): + value = "2024-01-01" + date_picker = vm.DatePicker(range=False, value=value) + expected_value = date(2024, 1, 1) + assert date_picker.value == expected_value + + @pytest.mark.parametrize("range, value", [(False, "2024-01-01"), (True, ["2024-01-01", "2024-02-01"])]) + def test_validate_datepicker_value_invalid(self, range, value): + with pytest.raises(ValidationError, match="Please provide a valid value between the min and max value."): + vm.DatePicker(min="1999-01-01", max="1999-02-01", range=range, value=value) + + @pytest.mark.parametrize("range, value", [(False, "2024-01-01"), (True, ["2024-01-01", "2024-02-01"])]) + def test_validate_datepicker_range_valid(self, range, value): + date_picker = vm.DatePicker(min="2024-01-01", max="2024-02-01", range=range, value=value) + assert date_picker.range == range + + @pytest.mark.parametrize( + "range, value", + [ + (True, "2024-01-01"), # range True produces DateRangePicker, value needs to be list with 2 dates + (True, ["2024-01-01"]), # range True produces DateRangePicker, value needs to be list with 2 dates + (False, ["2024-01-01"]), # range False produces DatePicker, value needs to be single date + (False, ["2024-01-01", "2024-03-01"]), # range False produces DatePicker, value needs to be single date + ], + ) + def test_validate_datepicker_range_invalid(self, range, value): + with pytest.raises(ValidationError): + vm.DatePicker(range=range, value=value) + + +class TestBuildMethod: + @pytest.mark.parametrize("range, value", [(False, "2023-01-05"), (True, ["2023-01-05", "2023-01-07"])]) + def test_datepicker_build(self, range, value): + date_picker = vm.DatePicker( + min="2023-01-01", max="2023-07-01", range=range, value=value, id="datepicker_id", title="Test title" + ).build() + + date_picker_class = dmc.DateRangePicker if range else dmc.DatePicker + additional_kwargs = {"allowSingleDateInRange": True} if range else {} + expected_datepicker = html.Div( + [ + html.Label("Test title", htmlFor="datepicker_id"), + date_picker_class( + id="datepicker_id", + minDate="2023-01-01", + maxDate="2023-07-02", + value=value, + persistence=True, + persistence_type="session", + dropdownPosition="bottom-start", + disabledDates="2023-07-02", + clearable=False, + className="datepicker", + **additional_kwargs, + ), + dcc.Store(id="datepicker_id_input_store", storage_type="session", data=value), + ], + className="selector_container", + id="datepicker_id_outer", + ) + assert_component_equal(date_picker, expected_datepicker) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py b/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py index 64fd8b904..1cd41affb 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py @@ -115,7 +115,7 @@ def test_create_radio_items_invalid_value_non_existing(self, test_value, options RadioItems(value=test_value, options=options) def test_create_radio_items_invalid_value_format(self): - with pytest.raises(ValidationError, match="3 validation errors for RadioItems"): + with pytest.raises(ValidationError, match="validation errors for RadioItems"): RadioItems(value=[1], options=[1, 2, 3, 4, 5]) def test_set_action_via_validator(self, identity_action_function): diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py b/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py index bb7314f35..61304e2eb 100644 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py @@ -179,7 +179,7 @@ def test_valid_min_max(self, min, max, expected_min, expected_max): def test_validate_max_invalid(self): with pytest.raises( - ValidationError, match="Maximum value of slider is required to be larger than minimum value." + ValidationError, match="Maximum value of selector is required to be larger than minimum value." ): vm.RangeSlider(min=10, max=0) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py b/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py index 35334646a..07cbbe3cc 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py @@ -84,7 +84,7 @@ def test_valid_min_max(self, min, max): def test_validate_max_invalid(self): with pytest.raises( - ValidationError, match="Maximum value of slider is required to be larger than minimum value." + ValidationError, match="Maximum value of selector is required to be larger than minimum value." ): vm.Slider(min=10, max=0) diff --git a/vizro-core/tests/unit/vizro/models/_controls/conftest.py b/vizro-core/tests/unit/vizro/models/_controls/conftest.py index 71c6ce4fe..b94c840dc 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/conftest.py +++ b/vizro-core/tests/unit/vizro/models/_controls/conftest.py @@ -1,3 +1,4 @@ +import datetime import random import numpy as np @@ -14,11 +15,13 @@ def dfs_with_shared_column(): df1["x"] = np.random.uniform(0, 10, 100) df1["y"] = np.random.uniform(0, 10, 100) df2 = df1.copy() + df3 = df1.copy() df1["shared_column"] = np.random.uniform(0, 10, 100) - df2["shared_column"] = random.choices(["CATEGORY 1", "CATEGORY 2"], k=100) + df2["shared_column"] = [datetime.datetime(2024, 1, 1) + datetime.timedelta(days=i) for i in range(100)] + df3["shared_column"] = random.choices(["CATEGORY 1", "CATEGORY 2"], k=100) - return df1, df2 + return df1, df2, df3 @pytest.fixture @@ -38,13 +41,14 @@ def managers_one_page_two_graphs(gapminder): @pytest.fixture def managers_shared_column_different_dtype(dfs_with_shared_column): """Instantiates the managers with a page and two graphs sharing the same column but of different data types.""" - df1, df2 = dfs_with_shared_column + df1, df2, df3 = dfs_with_shared_column vm.Page( id="graphs_with_shared_column", title="Page Title", components=[ - vm.Graph(figure=px.scatter(df1, x="x", y="y", color="shared_column")), - vm.Graph(figure=px.scatter(df2, x="x", y="y", color="shared_column")), + vm.Graph(id="id_shared_column_numerical", figure=px.scatter(df1, x="x", y="y", color="shared_column")), + vm.Graph(id="id_shared_column_temporal", figure=px.scatter(df2, x="x", y="y", color="shared_column")), + vm.Graph(id="id_shared_column_categorical", figure=px.scatter(df3, x="x", y="y", color="shared_column")), ], ) Vizro._pre_build() diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py index a29834d70..e60f626e4 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py @@ -1,3 +1,7 @@ +import re +from datetime import date, datetime +from typing import Literal + import pandas as pd import pytest import vizro.models as vm @@ -25,6 +29,73 @@ def test_filter_between(self, data, value, expected): result = _filter_between(series, value) pd.testing.assert_series_equal(result, expected) + @pytest.mark.parametrize( + "data, value, expected", + [ + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-02-01", "2024-03-01"], + [False, True, True, False, False], + ), # Standard test + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-01-01", "2024-05-01"], + [True, True, True, True, True], + ), # Test with dates for inclusive both ends + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-06-01", "2024-07-01"], + [False, False, False, False, False], + ), # Test with no result + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-03-01", "2024-02-01"], + [False, False, False, False, False], + ), # Test for inverted values + ([], ["2024-02-01", "2024-03-01"], pd.Series([], dtype=bool)), # Test for empty series + ( + [ + datetime(2024, 1, 1, 20, 20, 20), + datetime(2024, 2, 1, 20, 20, 20), + datetime(2024, 3, 1, 20, 20, 20), + datetime(2024, 4, 1, 20, 20, 20), + datetime(2024, 5, 1, 20, 20, 20), + ], + ["2024-02-01", "2024-03-01"], + [False, True, True, False, False], + ), # Test with time part in the date + ], + ) + def test_filter_between_date(self, data, value, expected): + series = pd.Series(data) + expected = pd.Series(expected) + result = _filter_between(series, value) + pd.testing.assert_series_equal(result, expected) + @pytest.mark.parametrize( "data, value, expected", [ @@ -40,6 +111,72 @@ def test_filter_isin(self, data, value, expected): result = _filter_isin(series, value) pd.testing.assert_series_equal(result, expected) + @pytest.mark.parametrize( + "data, value, expected", + [ + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-02-01"], + [False, True, False, False, False], + ), # Standard test + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 2, 1), + datetime(2024, 2, 1), + ], + ["2024-02-01"], + [False, True, False, True, True], + ), # Multiple values + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + ["2024-06-01"], + [False, False, False, False, False], + ), # Test with no result + ( + [ + datetime(2024, 1, 1), + datetime(2024, 2, 1), + datetime(2024, 3, 1), + datetime(2024, 4, 1), + datetime(2024, 5, 1), + ], + [], + [False, False, False, False, False], + ), # Test for empty value list + ( + [ + datetime(2024, 1, 1, 20, 20, 20), + datetime(2024, 2, 1, 20, 20, 20), + datetime(2024, 3, 1, 20, 20, 20), + datetime(2024, 4, 1, 20, 20, 20), + datetime(2024, 5, 1, 20, 20, 20), + ], + ["2024-02-01"], + [False, True, False, False, False], + ), # Test with time part in the date + ], + ) + def test_filter_isin_date(self, data, value, expected): + series = pd.Series(data) + expected = pd.Series(expected) + result = _filter_isin(series, value) + pd.testing.assert_series_equal(result, expected) + @pytest.mark.usefixtures("managers_one_page_two_graphs") class TestFilterInstantiation: @@ -83,94 +220,190 @@ def test_set_targets_invalid(self, managers_one_page_two_graphs): filter.pre_build() @pytest.mark.parametrize( - "test_input,expected", [("country", "categorical"), ("year", "numerical"), ("lifeExp", "numerical")] + "filtered_column, expected_column_type", + [("country", "categorical"), ("year", "temporal"), ("lifeExp", "numerical")], ) - def test_set_column_type(self, test_input, expected, managers_one_page_two_graphs): - filter = vm.Filter(column=test_input) + def test_set_column_type(self, filtered_column, expected_column_type, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column) model_manager["test_page"].controls = [filter] filter.pre_build() - assert filter._column_type == expected + assert filter._column_type == expected_column_type @pytest.mark.parametrize( - "test_input,expected", [("country", vm.Dropdown), ("year", vm.RangeSlider), ("lifeExp", vm.RangeSlider)] + "filtered_column, expected_selector", + [("country", vm.Dropdown), ("year", vm.DatePicker), ("lifeExp", vm.RangeSlider)], ) - def test_set_selector(self, test_input, expected, managers_one_page_two_graphs): - filter = vm.Filter(column=test_input) + def test_set_selector_default_selector(self, filtered_column, expected_selector, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert isinstance(filter.selector, expected_selector) + assert filter.selector.title == filtered_column.title() + + @pytest.mark.parametrize("filtered_column", ["country", "year", "lifeExp"]) + def test_set_selector_specific_selector(self, filtered_column, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column, selector=vm.RadioItems(title="Title")) model_manager["test_page"].controls = [filter] filter.pre_build() - assert isinstance(filter.selector, expected) - assert filter.selector.title == test_input.title() + assert isinstance(filter.selector, vm.RadioItems) + assert filter.selector.title == "Title" - @pytest.mark.parametrize("test_input", [vm.Slider(), vm.RangeSlider()]) - def test_set_slider_values_incompatible_column_type(self, test_input, managers_one_page_two_graphs): - filter = vm.Filter(column="country", selector=test_input) + @pytest.mark.parametrize( + "filtered_column, selector", + [ + ("country", vm.Dropdown), + ("country", vm.RadioItems), + ("country", vm.Checklist), + ("lifeExp", vm.Slider), + ("lifeExp", vm.RangeSlider), + ("lifeExp", vm.Dropdown), + ("lifeExp", vm.RadioItems), + ("lifeExp", vm.Checklist), + ("year", vm.Dropdown), + ("year", vm.RadioItems), + ("year", vm.Checklist), + ("year", vm.DatePicker), + ], + ) + def test_allowed_selectors_per_column_type(self, filtered_column, selector, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column, selector=selector()) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert isinstance(filter.selector, selector) + + @pytest.mark.parametrize( + "filtered_column, selector", + [ + ("country", vm.Slider), + ("country", vm.RangeSlider), + ("country", vm.DatePicker), + ("lifeExp", vm.DatePicker), + ("year", vm.Slider), + ("year", vm.RangeSlider), + ], + ) + def test_disallowed_selectors_per_column_type(self, filtered_column, selector, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column, selector=selector()) model_manager["test_page"].controls = [filter] with pytest.raises( ValueError, - match=f"Chosen selector {test_input.type} is not compatible with categorical column '{filter.column}'.", + match=f"Chosen selector {selector().type} is not compatible with .* column '{filtered_column}'. ", ): filter.pre_build() - @pytest.mark.parametrize("test_input", [vm.Slider(), vm.RangeSlider()]) - def test_set_slider_values_shared_column_inconsistent_dtype( - self, test_input, managers_shared_column_different_dtype - ): - filter = vm.Filter(column="shared_column", selector=test_input) + @pytest.mark.parametrize( + "targets", + [ + ["id_shared_column_numerical", "id_shared_column_temporal"], + ["id_shared_column_numerical", "id_shared_column_categorical"], + ["id_shared_column_temporal", "id_shared_column_categorical"], + ], + ) + def test_set_slider_values_shared_column_inconsistent_dtype(self, targets, managers_shared_column_different_dtype): + filter = vm.Filter(column="shared_column", targets=targets) model_manager["graphs_with_shared_column"].controls = [filter] with pytest.raises( ValueError, - match=f"Non-numeric values detected in the shared data column '{filter.column}' for targeted charts. " - f"Please ensure that the data column contains the same data type across all targeted charts.", + match=re.escape( + f"Inconsistent types detected in the shared data column 'shared_column' for targeted charts {targets}. " + f"Please ensure that the data column contains the same data type across all targeted charts." + ), ): filter.pre_build() - @pytest.mark.parametrize("test_input", [vm.Slider(), vm.RangeSlider()]) - def test_set_slider_values_defaults_min_max_none(self, test_input, gapminder, managers_one_page_two_graphs): - filter = vm.Filter(column="lifeExp", selector=test_input) + @pytest.mark.parametrize("selector", [vm.Slider, vm.RangeSlider]) + def test_set_numerical_selectors_values_min_max_default(self, selector, gapminder, managers_one_page_two_graphs): + filter = vm.Filter(column="lifeExp", selector=selector()) model_manager["test_page"].controls = [filter] filter.pre_build() assert filter.selector.min == gapminder.lifeExp.min() assert filter.selector.max == gapminder.lifeExp.max() - @pytest.mark.parametrize("test_input", [vm.Slider(min=3, max=5), vm.RangeSlider(min=3, max=5)]) - def test_set_slider_values_defaults_min_max_fix(self, test_input, managers_one_page_two_graphs): - filter = vm.Filter(column="lifeExp", selector=test_input) + def test_set_temporal_selectors_values_min_max_default(self, gapminder, managers_one_page_two_graphs): + filter = vm.Filter(column="year", selector=vm.DatePicker()) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert filter.selector.min == gapminder.year.min().to_pydatetime().date() + assert filter.selector.max == gapminder.year.max().to_pydatetime().date() + + @pytest.mark.parametrize("selector", [vm.Slider, vm.RangeSlider]) + def test_set_numerical_selectors_values_min_max_specific(self, selector, managers_one_page_two_graphs): + filter = vm.Filter(column="lifeExp", selector=selector(min=3, max=5)) model_manager["test_page"].controls = [filter] filter.pre_build() assert filter.selector.min == 3 assert filter.selector.max == 5 - @pytest.mark.parametrize("test_input", [vm.Checklist(), vm.Dropdown(), vm.RadioItems()]) - def test_set_categorical_selectors_options_defaults_options_none( - self, test_input, gapminder, managers_one_page_two_graphs - ): - filter = vm.Filter(column="continent", selector=test_input) + def test_set_temporal_selectors_values_min_max_specific(self, managers_one_page_two_graphs): + filter = vm.Filter(column="year", selector=vm.DatePicker(min="1952-01-01", max="2007-01-01")) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert filter.selector.min == date(1952, 1, 1) + assert filter.selector.max == date(2007, 1, 1) + + @pytest.mark.parametrize("selector", [vm.Checklist, vm.Dropdown, vm.RadioItems]) + def test_set_categorical_selectors_options_default(self, selector, gapminder, managers_one_page_two_graphs): + filter = vm.Filter(column="continent", selector=selector()) model_manager["test_page"].controls = [filter] filter.pre_build() assert filter.selector.options == sorted(set(gapminder["continent"])) + @pytest.mark.parametrize("selector", [vm.Checklist, vm.Dropdown, vm.RadioItems]) + def test_set_categorical_selectors_options_specific(self, selector, managers_one_page_two_graphs): + filter = vm.Filter(column="continent", selector=selector(options=["Africa", "Europe"])) + model_manager["test_page"].controls = [filter] + filter.pre_build() + assert filter.selector.options == ["Africa", "Europe"] + @pytest.mark.parametrize( - "test_input", + "filtered_column, selector, filter_function", [ - vm.Checklist(options=["Africa", "Europe"]), - vm.Dropdown(options=["Africa", "Europe"]), - vm.RadioItems(options=["Africa", "Europe"]), + ("lifeExp", None, _filter_between), + ("country", None, _filter_isin), + ("year", None, _filter_between), + ("year", vm.DatePicker(range=False), _filter_isin), ], ) - def test_set_categorical_selectors_options_defaults_options_fix(self, test_input, managers_one_page_two_graphs): - filter = vm.Filter(column="continent", selector=test_input) + def test_set_actions(self, filtered_column, selector, filter_function, managers_one_page_two_graphs): + filter = vm.Filter(column=filtered_column, selector=selector) model_manager["test_page"].controls = [filter] filter.pre_build() - assert filter.selector.options == ["Africa", "Europe"] + default_action = filter.selector.actions[0] + assert isinstance(default_action, ActionsChain) + assert isinstance(default_action.actions[0].function, CapturedCallable) + assert default_action.actions[0].function["filter_function"] == filter_function + assert default_action.actions[0].id == f"filter_action_{filter.id}" + + # TODO: Add tests for custom temporal and categorical selectors too. Probably inside the conftest file and reused in + # all other tests. Also add tests for the custom selector that is an entirely new component and adjust docs. + def test_numerical_custom_selector(self, gapminder, managers_one_page_two_graphs): + class RangeSliderNonCross(vm.RangeSlider): + """Custom numerical multi-selector `RangeSliderNonCross` to be provided to `Filter`.""" - @pytest.mark.parametrize("test_input", ["country", "year", "lifeExp"]) - def test_set_actions(self, test_input, managers_one_page_two_graphs): - filter = vm.Filter(column=test_input) + type: Literal["range_slider_non_cross"] = "range_slider_non_cross" + + def build(self): + range_slider_build_obj = super().build() + range_slider_build_obj[self.id].allowCross = False + return range_slider_build_obj + + filtered_column = "lifeExp" + selector = RangeSliderNonCross + vm.Filter.add_type("selector", selector) + + filter = vm.Filter(column=filtered_column, selector=selector()) model_manager["test_page"].controls = [filter] filter.pre_build() + + assert isinstance(filter.selector, selector) + assert filter.selector.title == filtered_column.title() + assert filter.selector.min == gapminder.lifeExp.min() + assert filter.selector.max == gapminder.lifeExp.max() + default_action = filter.selector.actions[0] assert isinstance(default_action, ActionsChain) assert isinstance(default_action.actions[0].function, CapturedCallable) + assert default_action.actions[0].function["filter_function"] == _filter_between assert default_action.actions[0].id == f"filter_action_{filter.id}" @@ -186,6 +419,8 @@ class TestFilterBuild: ("continent", vm.RadioItems()), ("pop", vm.RangeSlider()), ("pop", vm.Slider()), + ("year", vm.DatePicker()), + ("year", vm.DatePicker(range=False)), ], ) def test_filter_build(self, test_column, test_selector): diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py index 71e0a3052..eeb687962 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py @@ -62,6 +62,26 @@ def test_set_target_and_title_valid(self, test_input, title): assert parameter.targets == ["scatter_chart.x"] assert parameter.selector.title == title + @pytest.mark.parametrize("test_input", [vm.Slider(), vm.RangeSlider(), vm.DatePicker()]) + def test_numerical_and_temporal_selectors_missing_values(self, test_input): + parameter = Parameter(targets=["scatter_chart.x"], selector=test_input) + page = model_manager["test_page"] + page.controls = [parameter] + with pytest.raises( + TypeError, match=f"{test_input.type} requires the arguments 'min' and 'max' when used within Parameter." + ): + parameter.pre_build() + + @pytest.mark.parametrize("test_input", [vm.Checklist(), vm.Dropdown(), vm.RadioItems()]) + def test_categorical_selectors_with_missing_options(self, test_input): + parameter = Parameter(targets=["scatter_chart.x"], selector=test_input) + page = model_manager["test_page"] + page.controls = [parameter] + with pytest.raises( + TypeError, match=f"{parameter.selector.type} requires the argument 'options' when used within Parameter." + ): + parameter.pre_build() + @pytest.mark.parametrize( "test_input", [ @@ -80,26 +100,6 @@ def test_set_actions(self, test_input): assert isinstance(default_action.actions[0].function, CapturedCallable) assert default_action.actions[0].id == f"parameter_action_{parameter.id}" - @pytest.mark.parametrize("test_input", [vm.Slider(), vm.RangeSlider()]) - def test_set_slider_values_invalid(self, test_input): - parameter = Parameter(targets=["scatter_chart.x"], selector=test_input) - page = model_manager["test_page"] - page.controls = [parameter] - with pytest.raises( - TypeError, match=f"{test_input.type} requires the arguments 'min' and 'max' when used within Parameter." - ): - parameter.pre_build() - - @pytest.mark.parametrize("test_input", [vm.Checklist(), vm.Dropdown(), vm.RadioItems()]) - def test_set_categorical_selectors_with_missing_options(self, test_input): - parameter = Parameter(targets=["scatter_chart.x"], selector=test_input) - page = model_manager["test_page"] - page.controls = [parameter] - with pytest.raises( - TypeError, match=f"{parameter.selector.type} requires the argument 'options' when used within Parameter." - ): - parameter.pre_build() - @pytest.mark.usefixtures("managers_one_page_two_graphs") class TestParameterBuild: