-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] Enable that components can be nested inside basic type structures too #929
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<!-- | ||
A new scriv changelog fragment. | ||
|
||
Uncomment the section that is right (remove the HTML comment wrapper). | ||
--> | ||
|
||
<!-- | ||
### Highlights ✨ | ||
|
||
- A bullet item for the Highlights ✨ category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> | ||
<!-- | ||
### Removed | ||
|
||
- A bullet item for the Removed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> | ||
<!-- | ||
### Added | ||
|
||
- A bullet item for the Added category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> | ||
<!-- | ||
### Changed | ||
|
||
- A bullet item for the Changed category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> | ||
<!-- | ||
### Deprecated | ||
|
||
- A bullet item for the Deprecated category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> | ||
|
||
### Fixed | ||
|
||
- Enable that custom components can be nested arbitrarily deep inside basic type structures (e.g. lists within lists), and not just specific attributes. ([#929](https://github.com/mckinsey/vizro/pull/929)) | ||
|
||
|
||
<!-- | ||
### Security | ||
|
||
- A bullet item for the Security category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX. ([#1](https://github.com/mckinsey/vizro/pull/1)) | ||
|
||
--> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,69 @@ | ||
"""Dev app to try things out.""" | ||
|
||
from vizro import Vizro | ||
import vizro.plotly.express as px | ||
import vizro.models as vm | ||
from vizro.tables import dash_ag_grid | ||
import pandas as pd | ||
import vizro.models as vm | ||
import vizro.plotly.express as px | ||
from vizro import Vizro | ||
from typing import List, Literal, Tuple | ||
from vizro.models.types import ControlType | ||
from dash import html | ||
|
||
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv") | ||
columnDefs = [ | ||
{"field": "athlete", "headerName": "The full Name of the athlete"}, | ||
{"field": "age", "headerName": "The number of Years since the athlete was born"}, | ||
{"field": "country", "headerName": "The Country the athlete was born in"}, | ||
{"field": "sport", "headerName": "The Sport the athlete participated in"}, | ||
{"field": "total", "headerName": "The Total number of medals won by the athlete"}, | ||
] | ||
|
||
defaultColDef = { | ||
"wrapHeaderText": True, | ||
"autoHeaderHeight": True, | ||
} | ||
class CustomGroup(vm.VizroBaseModel): | ||
"""Container to group controls.""" | ||
|
||
type: Literal["custom_group"] = "custom_group" | ||
controls: List[Tuple[str, List[ControlType]]] = [[]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI my preferred pattern for this would be to nest a list of models, each of which does this (a bit like tabs):
Or if you want just one model then do it like this:
As it stands we made the deliberate choice in vizro to avoid |
||
|
||
def build(self): | ||
return html.Div( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Amazing, how this works now! 🥳 🍾 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See below but crucially this only works because In future that won't be a limitation but for now it's ok. |
||
children=[ | ||
html.Div( | ||
children=[html.Br(), html.H5(control_tuple[0]), *[control.build() for control in control_tuple[1]]], | ||
) | ||
for control_tuple in self.controls | ||
] | ||
) | ||
|
||
|
||
vm.Page.add_type("controls", CustomGroup) | ||
|
||
|
||
# Test app ----------------- | ||
page = vm.Page( | ||
title="Page Title", | ||
components=[vm.AgGrid(figure=dash_ag_grid(df, columnDefs=columnDefs, defaultColDef=defaultColDef))], | ||
title="Title", | ||
components=[ | ||
vm.Graph(id="graph_id", figure=px.scatter(px.data.iris(), x="sepal_width", y="sepal_length", color="species")), | ||
], | ||
controls=[ | ||
CustomGroup( | ||
controls=[ | ||
( | ||
"Categorical Filters", | ||
[ | ||
vm.Filter(column="species"), | ||
], | ||
), | ||
( | ||
"Numeric Filters", | ||
[ | ||
vm.Filter(column="petal_length"), | ||
vm.Filter(column="sepal_length"), | ||
], | ||
), | ||
], | ||
), | ||
vm.Parameter( | ||
targets=["graph_id.x"], | ||
selector=vm.RadioItems( | ||
title="Select X Axis", | ||
options=["sepal_width", "sepal_length", "petal_width", "petal_length"], | ||
value="sepal_width", | ||
), | ||
), | ||
], | ||
) | ||
|
||
|
||
dashboard = vm.Dashboard(pages=[page]) | ||
|
||
if __name__ == "__main__": | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -84,28 +84,25 @@ def _get_models( | |||||||||||||||||||||||||||||||||||
def __get_model_children(self, model: Model) -> Generator[Model, None, None]: | ||||||||||||||||||||||||||||||||||||
"""Iterates through children of `model`. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Currently looks only through certain fields so might miss some children models. | ||||||||||||||||||||||||||||||||||||
Currently, this method looks only through certain fields so might miss some children models. | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
What does that mean that it looks "through certain fields"? I thought it's independent of "components", "tabs", "controls", "actions", "selector" now. Or does it refer to something else? If I understood correctly, it just misses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It's still not independent of the fields now - unless I missed something the PR description is actually wrong when it says it works with What has actually changed is:
|
||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||
from vizro.models import VizroBaseModel | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if isinstance(model, VizroBaseModel): | ||||||||||||||||||||||||||||||||||||
yield model | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
# We don't handle dicts of models at the moment. See below TO-DOs for how this will all be improved in future. | ||||||||||||||||||||||||||||||||||||
if isinstance(model, (list, tuple)): | ||||||||||||||||||||||||||||||||||||
for single_model in model: | ||||||||||||||||||||||||||||||||||||
yield from self.__get_model_children(single_model) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Comment on lines
91
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some proposed changes:
Suggested change
|
||||||||||||||||||||||||||||||||||||
# TODO: in future this list should not be maintained manually. Instead we should look through all model children | ||||||||||||||||||||||||||||||||||||
# by looking at model.model_fields. | ||||||||||||||||||||||||||||||||||||
# by looking at model.model_fields. | ||||||||||||||||||||||||||||||||||||
model_fields = ["components", "tabs", "controls", "actions", "selector"] | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
for model_field in model_fields: | ||||||||||||||||||||||||||||||||||||
if (model_field_value := getattr(model, model_field, None)) is not None: | ||||||||||||||||||||||||||||||||||||
if isinstance(model_field_value, list): | ||||||||||||||||||||||||||||||||||||
# For fields like components that are list of models. | ||||||||||||||||||||||||||||||||||||
for single_model_field_value in model_field_value: | ||||||||||||||||||||||||||||||||||||
yield from self.__get_model_children(single_model_field_value) | ||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||
# For fields that have single model like selector. | ||||||||||||||||||||||||||||||||||||
yield from self.__get_model_children(model_field_value) | ||||||||||||||||||||||||||||||||||||
# We don't handle dicts of models at the moment. See below TODO for how this will all be improved in | ||||||||||||||||||||||||||||||||||||
# future. | ||||||||||||||||||||||||||||||||||||
yield from self.__get_model_children(model_field_value) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
# TODO: Add navigation, accordions and other page objects. Won't be needed once have made whole model | ||||||||||||||||||||||||||||||||||||
# manager work better recursively and have better ways to navigate the hierarchy. In pydantic v2 this would use | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rephrase to below? I tried to simplify a bit