Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3f4c494
Backend support for Dropdown in dash==4.0.0rc2 (without tests)
petar-qb Oct 28, 2025
95a7dab
Remove dropdown.test.js
petar-qb Oct 28, 2025
af983cf
Minor
petar-qb Oct 28, 2025
bf14837
Merge branch 'main' into tidy/backend_for_v4_dd
huong-li-nguyen Nov 17, 2025
cdf2b19
[Tidy] Update styling of `dcc` components in `dash>=4` (#1489)
huong-li-nguyen Nov 17, 2025
eb2f092
Remove unit tests related to optionHeight and All logic (as removed)
huong-li-nguyen Nov 17, 2025
d3c9553
Delete js tests for sliders as functionality embedded by `dcc`
huong-li-nguyen Nov 17, 2025
728825c
Merge branch 'main' into tidy/backend_for_v4_dd
huong-li-nguyen Nov 25, 2025
9b7bb0c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 25, 2025
be080b4
Lint
huong-li-nguyen Nov 25, 2025
a3291e4
Turon on dots and simplify CSS
huong-li-nguyen Nov 25, 2025
ef5726b
Tidy
huong-li-nguyen Nov 25, 2025
e99a11f
Merge branch 'tidy/backend_for_v4_dd' of https://github.com/mckinsey/…
huong-li-nguyen Nov 25, 2025
fb5e2dd
Fix unit tests and linting
huong-li-nguyen Nov 25, 2025
a72c80e
Lint
huong-li-nguyen Nov 25, 2025
003b0bf
Merge main with the feature branch
petar-qb Dec 18, 2025
157641a
Update hatch.toml and pyproject.toml dependencies to dash==4.0.0rc5
petar-qb Dec 18, 2025
da132e1
Merge main into the feature branch
petar-qb Jan 19, 2026
ca867be
Upgrade to dash==4.0.0rc6
petar-qb Jan 19, 2026
b7f2af0
Revert type changes in Dropdown
petar-qb Jan 19, 2026
a55cf88
Add scratch with all selectors
petar-qb Jan 19, 2026
6697df0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 19, 2026
4e37ec9
Merge branch 'main' into tidy/backend_for_v4_dd
huong-li-nguyen Jan 20, 2026
3c56946
Fix dropdowns
huong-li-nguyen Jan 20, 2026
9fe737d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 66 additions & 8 deletions vizro-core/examples/scratch_dev/app.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,78 @@
import vizro.plotly.express as px
import numpy as np
import pandas as pd
import vizro.models as vm
import vizro.plotly.express as px
from dash_ag_grid import AgGrid
from vizro import Vizro
from vizro.models.types import capture
from vizro.managers import data_manager

data_manager["static_df"] = px.data.iris()
data_manager["dynamic_df"] = lambda number_of_points=10: px.data.iris().head(number_of_points)


SPECIES_COLORS = {"setosa": "#00b4ff", "versicolor": "#ff9222", "virginica": "#3949ab"}

df = px.data.iris()

page_1 = vm.Page(
title="BUG theme switch doesn't work with Flex layout",
layout=vm.Flex(),
title="Single/Multi static DD",
components=[
vm.Graph(figure=px.scatter(data_frame=df, x="sepal_width", y="sepal_length")),
vm.Graph(figure=px.scatter(data_frame=df.head(10), x="sepal_width", y="sepal_length")),
vm.Card(text="test"),
vm.Graph(
figure=px.scatter(
"static_df", x="sepal_width", y="sepal_length", color="species", color_discrete_map=SPECIES_COLORS
)
)
],
controls=[
vm.Filter(column="species", selector=vm.Dropdown(multi=True)),
vm.Filter(column="species", selector=vm.Dropdown(multi=False)),
],
)

dashboard = vm.Dashboard(pages=[page_1])
page_2 = vm.Page(
title="Single/Multi dynamic DD",
components=[
vm.Graph(
id="graph_2",
figure=px.scatter(
"dynamic_df", x="sepal_width", y="sepal_length", color="species", color_discrete_map=SPECIES_COLORS
),
)
],
controls=[
# TODO-REVIEWER: To a bug with the standard _build_dynamic_placeholder:
# 1. Replace vm.Dropdown._build_dynamic_placeholder with vm.Checklist._build_dynamic_placeholder implementation
# 2. Run the app and open page_2
# 3. Select values ["setosa", "versicolor"] in the multi Dropdown, and "versicolor" in the single Dropdown
# 4. Refresh the page.
vm.Filter(column="species", selector=vm.Dropdown(multi=True)),
vm.Filter(column="species", selector=vm.Dropdown(multi=False)),
vm.Parameter(
targets=["graph_2.data_frame.number_of_points"], selector=vm.Slider(min=10, max=150, step=10, value=10)
),
],
)


long_df = pd.DataFrame(
{
# TODO-UI: By setting max width of the Dropdown seems like option height is automatically adjusted.
"long_string": [
# List of 100 strings with variable number (1 -> 101) of characters "A"(95%) or space " "(5%).
"Value " + "".join(" " if np.random.rand() < 0.05 else "A" for _ in range(length))
for length in range(1, 101)
],
"x": range(1, 101),
"y": range(101, 201),
}
)

page_3 = vm.Page(
title="Long values in Dropdown",
components=[vm.Graph(figure=px.scatter(long_df, x="x", y="y"))],
controls=[vm.Filter(column="long_string")],
)

dashboard = vm.Dashboard(pages=[page_1, page_2, page_3])
if __name__ == "__main__":
Vizro().build(dashboard).run()
2 changes: 1 addition & 1 deletion vizro-core/hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ VIZRO_LOG_LEVEL = "DEBUG"

[envs.lower-bounds]
extra-dependencies = [
"dash==3.1.1",
"dash==4.0.0rc2",
"dash-bootstrap-components==2.0.0",
"dash-ag-grid==31.3.1",
"dash-mantine-components==1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion vizro-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python :: 3.13"
]
dependencies = [
"dash>=3.1.1,<4", # temporary upper bound to avoid breaking changes in Dash v4
"dash==4.0.0rc2", # temporary upper bound to avoid breaking changes in Dash v4
"dash_bootstrap_components>=2", # 2.0.0 needed to support dash>=3.0.0
"dash-ag-grid>=31.3.1", # 31.3.1 needed to support dash>=3.0.0
"dash_mantine_components>=1", # 1.0.0 needed to support dash>=3.0.0
Expand Down
86 changes: 8 additions & 78 deletions vizro-core/src/vizro/models/_components/form/dropdown.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import math
from datetime import date
from typing import Annotated, Any, Literal, Optional, Union, cast
from typing import Annotated, Any, Literal, Optional, Union

import dash_bootstrap_components as dbc
from dash import ClientsideFunction, Input, Output, State, clientside_callback, dcc, html
from pydantic import AfterValidator, BeforeValidator, Field, PrivateAttr, StrictBool, ValidationInfo, model_validator
from dash import dcc, html
from pydantic import AfterValidator, BeforeValidator, Field, PrivateAttr, ValidationInfo, model_validator
from pydantic.json_schema import SkipJsonSchema

from vizro.models import Tooltip, VizroBaseModel
Expand Down Expand Up @@ -33,24 +31,6 @@ def validate_multi(multi, info: ValidationInfo):
return multi


def _get_list_of_labels(full_options: OptionsType) -> Union[list[StrictBool], list[float], list[str], list[date]]:
"""Returns a list of labels from the selector options provided."""
if all(isinstance(option, dict) for option in full_options):
return [option["label"] for option in full_options] # type: ignore[index]
else:
return cast(Union[list[StrictBool], list[float], list[str], list[date]], full_options)


def _calculate_option_height(full_options: OptionsType, char_count: int) -> int:
"""Calculates the height of the dropdown options based on the longest option."""
# We look at the longest option to find number_of_lines it requires. Option height is the same for all options
# and needs 24px for each line + 8px padding.
list_of_labels = _get_list_of_labels(full_options)
max_length = max(len(str(option)) for option in list_of_labels)
number_of_lines = math.ceil(max_length / char_count)
return 8 + 24 * number_of_lines


class Dropdown(VizroBaseModel):
"""Categorical single/multi-option selector `Dropdown`.

Expand Down Expand Up @@ -148,41 +128,17 @@ def _action_inputs(self) -> dict[str, _IdProperty]:

def __call__(self, options):
dict_options, default_value = get_dict_options_and_default(options=options, multi=self.multi)
# 24 characters is roughly the number of "A" characters you can fit comfortably on a line in the page dropdown
# (placed on the left-side 280px width). 15 is the width for when the dropdown is in a container's controls.
# "A" is representative of a slightly wider than average character:
# https://stackoverflow.com/questions/3949422/which-letter-of-the-english-alphabet-takes-up-most-pixels
option_height = _calculate_option_height(dict_options, 15 if self._in_container else 24)

value = self.value if self.value is not None else default_value

if self.multi:
self._update_dropdown_select_all()
value = value if isinstance(value, list) else [value] # type: ignore[assignment]
dict_options = [
{
"label": dbc.Checkbox(
id=f"{self.id}_select_all",
value=len(value) == len(dict_options), # type: ignore[arg-type]
label="Select All",
persistence=True,
persistence_type="session",
className="dropdown-select-all",
),
# Special sentinel value used in update_dropdown_select_all.
# This never gets sent to the server.
"value": "__SELECT_ALL",
},
*dict_options,
]
if self.multi and not isinstance(value, list):
value = [value]

description = self.description.build().children if self.description else [None]
defaults = {
"id": self.id,
"options": dict_options,
"value": value,
"multi": self.multi,
"optionHeight": option_height,
"persistence": True,
"persistence_type": "session",
"placeholder": "Select option",
Expand Down Expand Up @@ -214,50 +170,24 @@ def _build_dynamic_placeholder(self):
# because placeholder for the Dropdown can't be the dropdown itself. The reason is that the Dropdown value can
# be unexpectedly changed when the new options are added. This is developed as the dash feature
# https://github.com/plotly/dash/pull/1970.
if self.multi:
# Add the clientside callback as the callback has to be defined in the page.build process.
self._update_dropdown_select_all()
# hidden_select_all_dropdown is needed to ensure that clientside callback doesn't raise the no output error.
hidden_select_all_dropdown = [dcc.Dropdown(id=f"{self.id}_select_all", style={"display": "none"})]
placeholder_model = dcc.Checklist
placeholder_options = self.value
else:
hidden_select_all_dropdown = [None]
placeholder_model = dbc.RadioItems
placeholder_options = [self.value] # type: ignore[assignment]
plh_model, plh_options = (dcc.Checklist, self.value) if self.multi else (dbc.RadioItems, [self.value])

description = self.description.build().children if self.description else [None]
return html.Div(
children=[
dbc.Label(
children=[html.Span(id=f"{self.id}_title", children=self.title), *description], html_for=self.id
),
placeholder_model(
plh_model(
id=self.id,
options=placeholder_options,
options=plh_options,
value=self.value,
persistence=True,
persistence_type="session",
),
*hidden_select_all_dropdown,
]
)

@_log_call
def build(self):
return self._build_dynamic_placeholder() if self._dynamic else self.__call__(self.options)

def _update_dropdown_select_all(self):
"""Define the clientside callbacks in the page build phase responsible for handling the select_all."""
clientside_callback(
ClientsideFunction(namespace="dropdown", function_name="update_dropdown_select_all"),
output=[
Output(f"{self.id}_select_all", "value"),
Output(self.id, "value", allow_duplicate=True),
],
inputs=[
Input(self.id, "value"),
State(self.id, "options"),
],
prevent_initial_call="initial_duplicate",
)
44 changes: 0 additions & 44 deletions vizro-core/src/vizro/static/js/models/dropdown.js

This file was deleted.

87 changes: 0 additions & 87 deletions vizro-core/tests/js/models/dropdown.test.js

This file was deleted.

Loading