From b0b9268f424141795ebd821e1d04a8f4d0592d53 Mon Sep 17 00:00:00 2001 From: petar-qb Date: Wed, 5 Jun 2024 16:30:25 +0200 Subject: [PATCH 1/2] PoC feat/update_filter_options_routing_callback_inputs --- vizro-core/examples/_dev/app.py | 76 +++++++++++++++++-- .../models/_components/form/range_slider.py | 10 +-- .../vizro/models/_components/form/slider.py | 10 +-- .../src/vizro/models/_controls/filter.py | 21 ++++- .../src/vizro/models/_controls/parameter.py | 2 +- vizro-core/src/vizro/models/_dashboard.py | 8 +- vizro-core/src/vizro/models/_page.py | 4 +- 7 files changed, 102 insertions(+), 29 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 623e86254..dbb401bef 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,26 +1,86 @@ """Example to show dashboard configuration.""" + +# TODO: Return an empty graph from parameter action if data_frame function argument in targeted. +# TODO: Implement a logic to pass certain kwargs to certain data sources +# TODO: move clientside callbacks to slider/range_slider pre_build method or something similar. + + import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro +from vizro.managers import data_manager +from vizro.actions import filter_interaction + + +def load_data(points=0): + return px.data.iris().head(points) + + +data_manager['my_data'] = load_data +graph_config = dict(x="sepal_length", y="petal_width", color="species") -df = px.data.iris() page = vm.Page( title="My first dashboard", components=[ - # components consist of vm.Graph or vm.Table - vm.Graph(id="scatter_chart", figure=px.scatter(df, x="sepal_length", y="petal_width", color="species")), - vm.Graph(id="hist_chart", figure=px.histogram(df, x="sepal_width", color="species")), + vm.Graph( + id="scatter_chart", + figure=px.scatter('my_data', **graph_config) + ), + vm.Graph( + id="scatter_chart_2", + figure=px.scatter(px.data.iris().tail(50), custom_data=["species"], **graph_config), + actions=[ + vm.Action( + function=filter_interaction(targets=["scatter_chart_3"]), + ) + ] + ), + vm.Graph( + id="scatter_chart_3", + figure=px.scatter(px.data.iris(), **graph_config), + ), ], controls=[ - # controls consist of vm.Filter or vm.Parameter - # filter the dataframe (df) of the target graph (histogram), by column sepal_width, using the dropdown - vm.Filter(column="sepal_width", selector=vm.Dropdown(), targets=["hist_chart"]), + vm.Filter(column="species", targets=["scatter_chart", "scatter_chart_2"]), + vm.Parameter( + targets=['scatter_chart.data_frame.points'], + selector=vm.Slider( + id="parameter_points", + min=0, + max=150, + step=10, + value=70 + ) + ), ], ) +# TODO: Consider adding a new Dashboard argument called `components`, where any dash component could be added to +# the global container. dashboard = vm.Dashboard(pages=[page]) if __name__ == "__main__": - Vizro().build(dashboard).run() + from dash import Input, Output, dcc, callback + + app = Vizro( + routing_callback_inputs={ + "points": Input("parameter_points_store", "data"), + } + ) + + app.build(dashboard) + + app.dash.layout.children.append(dcc.Store(id="parameter_points_store", data=0)) + + @callback( + Output("parameter_points_store", "data"), + Input("parameter_points", "value") + ) + def update_parameter_points_store(value): + # from time import sleep + # sleep(0.5) + return value + + app.run() 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 34c1f98f4..56ba238e3 100644 --- a/vizro-core/src/vizro/models/_components/form/range_slider.py +++ b/vizro-core/src/vizro/models/_components/form/range_slider.py @@ -78,11 +78,11 @@ def build(self): State(f"{self.id}_callback_data", "data"), ] - clientside_callback( - ClientsideFunction(namespace="clientside", function_name="update_range_slider_values"), - output=output, - inputs=inputs, - ) + # clientside_callback( + # ClientsideFunction(namespace="clientside", function_name="update_range_slider_values"), + # output=output, + # inputs=inputs, + # ) return html.Div( children=[ diff --git a/vizro-core/src/vizro/models/_components/form/slider.py b/vizro-core/src/vizro/models/_components/form/slider.py index 33dcc4057..fc9c0b27d 100644 --- a/vizro-core/src/vizro/models/_components/form/slider.py +++ b/vizro-core/src/vizro/models/_components/form/slider.py @@ -74,11 +74,11 @@ def build(self): State(f"{self.id}_callback_data", "data"), ] - clientside_callback( - ClientsideFunction(namespace="clientside", function_name="update_slider_values"), - output=output, - inputs=inputs, - ) + # clientside_callback( + # ClientsideFunction(namespace="clientside", function_name="update_slider_values"), + # output=output, + # inputs=inputs, + # ) return html.Div( children=[ diff --git a/vizro-core/src/vizro/models/_controls/filter.py b/vizro-core/src/vizro/models/_controls/filter.py index 5ae2e0f13..28fdf59e1 100644 --- a/vizro-core/src/vizro/models/_controls/filter.py +++ b/vizro-core/src/vizro/models/_controls/filter.py @@ -104,7 +104,17 @@ def pre_build(self): self._set_actions() @_log_call - def build(self): + def build(self, **kwargs): + # if self.dynamic: + # Effectively modifies state in model_manager, hence won't be shared across processes - is this ok? Yes + # because just gets updated on calling process so will be updated every time. No caching in terms of + # setting options, just in terms of data loading. + # Setting options itself could be heavy processing as relies on finding min/max but in that case should + # just set them upfront statically. + # In future could also add other things to data_manager cache like dataset's min/max for different columns + # in memoized way. But ultimately probably wouldn't want to use pandas for such situations anyway. + self._set_categorical_selectors_options(force_update=True, **kwargs) + return self.selector.build() def _set_targets(self): @@ -174,14 +184,17 @@ def _set_numerical_and_temporal_selectors_values(self): if self.selector.max is None: self.selector.max = max(max_values) - def _set_categorical_selectors_options(self): + def _set_categorical_selectors_options(self, force_update=False, **kwargs): # 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: + if isinstance(self.selector, SELECTORS["categorical"]) and (not self.selector.options or force_update): options = set() for target_id in self.targets: data_source_name = model_manager[target_id]["data_frame"] - data_frame = data_manager[data_source_name].load() + if target_id == "scatter_chart": + data_frame = data_manager[data_source_name].load(**kwargs) + else: + data_frame = data_manager[data_source_name].load() options |= set(data_frame[self.column]) self.selector.options = sorted(options) diff --git a/vizro-core/src/vizro/models/_controls/parameter.py b/vizro-core/src/vizro/models/_controls/parameter.py index 748a669b0..3f1f580f0 100644 --- a/vizro-core/src/vizro/models/_controls/parameter.py +++ b/vizro-core/src/vizro/models/_controls/parameter.py @@ -75,7 +75,7 @@ def pre_build(self): self._set_actions() @_log_call - def build(self): + def build(self, **kwargs): return self.selector.build() def _check_numerical_and_temporal_selectors_values(self): diff --git a/vizro-core/src/vizro/models/_dashboard.py b/vizro-core/src/vizro/models/_dashboard.py index ea65d5ebf..cbc9dcd3c 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -149,7 +149,7 @@ def build(self): className=self.theme, ) - def _get_page_divs(self, page: Page) -> _PageDivsType: + def _get_page_divs(self, page: Page, **kwargs) -> _PageDivsType: # Identical across pages dashboard_title = ( html.H2(id="dashboard-title", children=self.title) @@ -178,7 +178,7 @@ def _get_page_divs(self, page: Page) -> _PageDivsType: nav_panel = navigation["nav-panel"] # Different across pages - page_content: _PageBuildType = page.build() + page_content: _PageBuildType = page.build(**kwargs) control_panel = page_content["control-panel"] page_components = page_content["page-components"] @@ -234,8 +234,8 @@ def _arrange_page_divs(self, page_divs: _PageDivsType): page_main = html.Div(id="page-main", children=[collapsable_left_side, collapsable_icon, right_side]) return html.Div(children=[page_header, page_main], className="page-container") - def _make_page_layout(self, page: Page): - page_divs = self._get_page_divs(page=page) + def _make_page_layout(self, page: Page, **kwargs): + page_divs = self._get_page_divs(page=page, **kwargs) page_layout = self._arrange_page_divs(page_divs=page_divs) page_layout.id = page.id return page_layout diff --git a/vizro-core/src/vizro/models/_page.py b/vizro-core/src/vizro/models/_page.py index 9fc6340c5..b84eb6874 100644 --- a/vizro-core/src/vizro/models/_page.py +++ b/vizro-core/src/vizro/models/_page.py @@ -107,9 +107,9 @@ def pre_build(self): ] @_log_call - def build(self) -> _PageBuildType: + def build(self, **kwargs) -> _PageBuildType: self._update_graph_theme() - controls_content = [control.build() for control in self.controls] + controls_content = [control.build(**kwargs) for control in self.controls] control_panel = html.Div(id="control-panel", children=controls_content, hidden=not controls_content) components_container = self.layout.build() From ca3ffcf6fe645d12e13657e38b9657c1f457911c Mon Sep 17 00:00:00 2001 From: petar-qb Date: Thu, 20 Jun 2024 07:16:48 +0200 Subject: [PATCH 2/2] Add sleep --- vizro-core/examples/_dev/app.py | 6 +++--- vizro-core/src/vizro/models/_page.py | 2 ++ vizro-core/src/vizro/static/js/models/dashboard.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index dbb401bef..3f3bfc92b 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -79,8 +79,8 @@ def load_data(points=0): Input("parameter_points", "value") ) def update_parameter_points_store(value): - # from time import sleep - # sleep(0.5) + from time import sleep + sleep(0.5) return value - app.run() + app.run(debug=False) diff --git a/vizro-core/src/vizro/models/_page.py b/vizro-core/src/vizro/models/_page.py index cfbf69ac7..7f5defc86 100644 --- a/vizro-core/src/vizro/models/_page.py +++ b/vizro-core/src/vizro/models/_page.py @@ -108,6 +108,8 @@ def pre_build(self): @_log_call def build(self, **kwargs) -> _PageBuildType: + from time import sleep + sleep(0.5) controls_content = [control.build(**kwargs) for control in self.controls] control_panel = html.Div(id="control-panel", children=controls_content, hidden=not controls_content) diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index 6441723a6..4b4560194 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -30,7 +30,7 @@ export function _update_graph_theme(checked, vizro_themes, graph_id) { export function _collapse_nav_panel(n_clicks, is_open) { if (!n_clicks) { /* Automatically collapses left-side if xs and s-devices are detected*/ - if (window.innerWidth < 576 || window.innerHeight < 576) { + if (window.innerWidth < 176 || window.innerHeight < 176) { return [ false, {