diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index a1699fe5f..3f3bfc92b 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,23 +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) + -df = px.data.gapminder() +data_manager['my_data'] = load_data +graph_config = dict(x="sepal_length", y="petal_width", color="species") -page_one = vm.Page( - title="Vizro filters exporting", + +page = vm.Page( + title="My first dashboard", components=[ - vm.Graph(id="graph_1", figure=px.scatter(df, x="gdpPercap", y="lifeExp", color="continent", size="pop")), - vm.Graph(id="graph_2", figure=px.scatter(df, x="gdpPercap", y="lifeExp", color="continent", size="pop")), + 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=[ - vm.Filter(column="continent"), + 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 + ) + ), ], ) -dashboard = vm.Dashboard(pages=[page_one]) +# 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(debug=False) 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 050333e54..945145901 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -161,7 +161,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) @@ -190,7 +190,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"] @@ -246,8 +246,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 558d672c7..7f5defc86 100644 --- a/vizro-core/src/vizro/models/_page.py +++ b/vizro-core/src/vizro/models/_page.py @@ -107,8 +107,10 @@ def pre_build(self): ] @_log_call - def build(self) -> _PageBuildType: - controls_content = [control.build() for control in self.controls] + 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) components_container = self.layout.build() diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index a1c3e2131..9ec2732b6 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -36,7 +36,7 @@ export function _update_graph_theme( 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, {