Skip to content

Conversation

petar-qb
Copy link
Contributor

@petar-qb petar-qb commented Oct 6, 2025

Closes https://github.com/McK-Internal/vizro-internal/issues/2191

Description

Draft PR opened without fixing/writing any tests or docs.

TODO

  • - PoC
  • unit tests
  • js tests
  • fix screenshot tests
  • add new e2e tests:
    • dom
    • screenshot
    • http
  • Should we explain this feature in docs?
  • Should I adjust all docs screenshots so they contain reset button? -> ❌

Screenshot

  1. text-button
Screen.Recording.2025-10-06.at.15.47.32.mov
  1. icon-button with tooltip
Screen.Recording.2025-10-06.at.15.49.51.mov
  1. text-icon-button in control_panel or icon-with-tooltip in header (the current PR implementation)
Screen.Recording.2025-10-08.at.11.25.13.mov

Notice

  • I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":

    • I submit this contribution under the Apache 2.0 license and represent that I am entitled to do so on behalf of myself, my employer, or relevant third parties, as applicable.
    • I certify that (a) this contribution is my original creation and / or (b) to the extent it is not my original creation, I am authorized to submit this contribution on behalf of the original creator(s) or their licensees.
    • I certify that the use of this contribution as authorized by the Apache 2.0 license does not violate the intellectual property rights of anyone else.
    • I have not referenced individuals, products or companies in any commits, directly or indirectly.
    • I have not added data or restricted code in any commits, directly or indirectly.

@petar-qb petar-qb self-assigned this Oct 6, 2025
Copy link
Contributor

github-actions bot commented Oct 6, 2025

View the example dashboards of the current commit live on PyCafe ☕ 🚀

Updated on: 2025-10-06 13:15:21 UTC
Commit: e598a93

Compare the examples using the commit's wheel file vs the latest released version:

vizro-core/examples/scratch_dev

View with commit's wheel vs View with latest release

vizro-core/examples/dev/

View with commit's wheel vs View with latest release

vizro-core/examples/visual-vocabulary/

View with commit's wheel vs View with latest release

vizro-core/examples/tutorial/

View with commit's wheel vs View with latest release

Comment on lines 180 to 193
vizro_controls_store_data = {
page.id: {
control.id: {
"selectorId": control.selector.id,
"originalValue": control.selector.value,
}
for control in cast(
Iterable[ControlType],
[*model_manager._get_models(Parameter, page), *model_manager._get_models(Filter, page)],
)
}
for page in self.pages
}

Copy link
Contributor Author

@petar-qb petar-qb Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonymilne

I was able skip the page.id level of hierarchy and to move this to page.py (similarly how the page_id_store is added/handled), but I left it here on the dashboard level as this will be reused when someone starts working on the cross-page actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vizro_controls_store structure will likely be adjusted later as we add more reset buttons (container-level, dashboard-level, figure-level) or cross-page actions. A flat structure is a strong candidate to avoid deep hierarchies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the current structure where page.id is a top-level key done like this just so that reset_page_controls can easily look up all the controls on that page?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly. However I think that the structure we be adjusted later to something like:

{
  "control_1_id": {
    "selector_id": "selector_control_1_id",
    "page_id": "my_homepage",
    "parent_container_id": "container_id"
    "original_value": ["1", "2"],
    "current_value": ["1," "2", "3"],
    ...
  },
  "control_2_id": ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just refactored the code and switched to the format suggested in the previous comment. 😃

Comment on lines 305 to 345
has_page_controls = bool(
[*model_manager._get_models(Parameter, page), *model_manager._get_models(Filter, page)]
)

# Page header controls that appear on the right side of the header.
header_controls = html.Div(
id="header-controls",
children=[
dcc.Loading(
id="action-progress-indicator",
delay_show=300,
delay_hide=300,
custom_spinner=html.Span(
className="material-symbols-outlined progress-indicator",
# Keep "progress_activity" children so the CSS spinner can render/display correctly.
children="progress_activity",
),
# Placeholder div is added as used as target from actions to show loading indicator.
children=html.Div(id="action-progress-indicator-placeholder"),
),
# Show reset button in header when page controls exist but control panel does not exist
reset_page_controls_btn if has_page_controls and _all_hidden(control_panel) else None,
dbc.Switch(
id="theme-selector",
value=self.theme == "vizro_light",
persistence=True,
persistence_type="session",
),
],
)

# Apply different container position logic based on condition
if _all_hidden(header_left_content + header_right_content):
page_header_content.append(header_controls)
else:
header_right_content.append(header_controls)

# Show reset button in the control panel when page controls and control panel exist
if has_page_controls and not _all_hidden(control_panel):
control_panel.children.append(reset_page_controls_btn)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code covers all the cases when:

  • controls exist in the controls panel => reset button is in the control panel
  • controls exist but hidden => reset button is in the header (page/dashboard depending on whether the dashboard header exist)
  • there's no controls on the page => reset button is not added

)

if page_controls:
# TODO-AV2 D: Think about merging this with the URL callback when start working on cross-page actions.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment added so i can be cross linked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@antonymilne antonymilne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this looks great! Definitely it gets the preliminary "carry on as planned" from me 👍 The code looks really nice and simple overall and not too horribly hacky like we had worried. Great scratch_dev app for testing everything too 💯

Please could you quickly update https://github.com/McK-Internal/vizro-internal/issues/1719 to briefly explain what the approach is here and what alternatives we dismissed and why (can be very rough notes).

On the styles: I like the rectangular text button but maybe also with the icon in it - what does that look like?

I'm not a big fan of the button moving next to the theme switcher but if others prefer it then it's ok. The alternative would be to keep the button in the same place and just have a big empty left sidebar for it which is also not great I know. If we do move the button next to the theme switcher then maybe in that case I would make it just a circular button with icon so it's more in scale with the theme switcher. wdyt now we have a real live example to look at @huong-li-nguyen? Happy to go with whatever you prefer here.

Should we explain this feature in docs?

Yes, just a couple of sentences in controls.md should be good, no need for anything else.

Should I adjust all docs screenshots so they contain reset button?

No, that is just too painful.

Comment on lines 180 to 193
vizro_controls_store_data = {
page.id: {
control.id: {
"selectorId": control.selector.id,
"originalValue": control.selector.value,
}
for control in cast(
Iterable[ControlType],
[*model_manager._get_models(Parameter, page), *model_manager._get_models(Filter, page)],
)
}
for page in self.pages
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the current structure where page.id is a top-level key done like this just so that reset_page_controls can easily look up all the controls on that page?

@petar-qb
Copy link
Contributor Author

petar-qb commented Oct 8, 2025

I'm not a big fan of the button moving next to the theme switcher but if others prefer it then it's ok. The alternative would be to keep the button in the same place and just have a big empty left sidebar for it which is also not great I know. If we do move the button next to the theme switcher then maybe in that case I would make it just a circular button with icon so it's more in scale with the theme switcher. wdyt now we have a real live example to look at @huong-li-nguyen? Happy to go with whatever you prefer here.

I just pushed some changes and you can see how it looks like now in the third screen-recording in the PR description. So, the current implementation adds:

  • (if controls and control_panel) an icon-text-button without a tooltip (as it's obvious from the button text what happens) to the control_panel
  • (if controls and not control_panel) clickable icon with a tooltip to the header

I like the current solution. What do you think @huong-li-nguyen?

@petar-qb
Copy link
Contributor Author

petar-qb commented Oct 8, 2025

The only thing I’m currently uncertain about is whether we should commit to adding the reset button by default without an easy way of removing it. We plan (not immediately but it should be an ultimate goal) to expand the reset-controls functionality beyond the page level by adding it to the dashboard, container, control, and source figure levels.

However, making it a default behaviour (“set in stone”) might feel too intrusive for Vizro users, even now when it’s only applied at the page level.

I’d recommend an opt-in / opt-out approach instead, so users can easily enable or disable the button at different levels.

Here’s my suggested API design:

vm.Dashboard(show_reset_controls_button: bool = False)
vm.Page(show_reset_controls_button: bool = True)  # Maybe even set it to False by default.. or not
vm.Container(show_reset_controls_button: bool = False)
vm.Graph(show_reset_controls_button: bool = False)
vm.Filter(show_reset_control_button: bool = False)

In that case I would expose additional vm.Page argument and default it to False/True depending on what we agree.

I'd love to get you opinions on this: @antonymilne @huong-li-nguyen @Joseph-Perkins 🙏

)


def set_selector_default_value(control_selector: SelectorType) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach to calculating selectors' default values:

  1. During the creation of vizro_controls_store (dashboard.build), calculate the default value for all selectors using get_dict_options_and_default and other relevant methods for min/max.
  2. Implement a getter property (e.g get_default_value) for all selectors to access their current value.

NB: Default selector values cannot be calculated during the initialisation phase because options/min/max could be determined in the Filter.pre_build phase.

I found the current implementation good enough but we should rethink the solution when we deal with the https://github.com/McK-Internal/vizro-internal/issues/1554

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants