Skip to content
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

[POC] Feat/update filter options routing callback inputs #555

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 71 additions & 8 deletions vizro-core/examples/_dev/app.py
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 5 additions & 5 deletions vizro-core/src/vizro/models/_components/form/range_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand Down
10 changes: 5 additions & 5 deletions vizro-core/src/vizro/models/_components/form/slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand Down
21 changes: 17 additions & 4 deletions vizro-core/src/vizro/models/_controls/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion vizro-core/src/vizro/models/_controls/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions vizro-core/src/vizro/models/_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions vizro-core/src/vizro/models/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion vizro-core/src/vizro/static/js/models/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand Down
Loading