diff --git a/vizro-core/src/vizro/__init__.py b/vizro-core/src/vizro/__init__.py index cf2b583d7..6a77ff59c 100644 --- a/vizro-core/src/vizro/__init__.py +++ b/vizro-core/src/vizro/__init__.py @@ -14,7 +14,7 @@ __all__ = ["Vizro"] -__version__ = "0.1.29.dev0" +__version__ = "0.1.30.dev0" # For the below _css_dist and _js_dist to be used by Dash, they must be retrieved by dash.resources.Css.get_all_css(). diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 1873b6620..984359fc9 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -49,18 +49,18 @@ def _get_component_actions(component) -> list[Action]: def _apply_filter_controls( - data_frame: pd.DataFrame, ctds_filter: list[CallbackTriggerDict], target: ModelID + data_frame: pd.DataFrame, ctds_filters: list[CallbackTriggerDict], target: ModelID ) -> pd.DataFrame: """Applies filters from a vm.Filter model in the controls. Args: data_frame: unfiltered DataFrame. - ctds_filter: list of CallbackTriggerDict for filters. + ctds_filters: list of CallbackTriggerDict for filters. target: id of targeted Figure. Returns: filtered DataFrame. """ - for ctd in ctds_filter: + for ctd in ctds_filters: selector_value = ctd["value"] selector_value = selector_value if isinstance(selector_value, list) else [selector_value] selector_actions = _get_component_actions(model_manager[ctd["id"]]) @@ -162,12 +162,12 @@ def _update_nested_figure_properties( def _get_parametrized_config( - ctds_parameter: list[CallbackTriggerDict], target: ModelID, data_frame: bool + ctd_parameters: list[CallbackTriggerDict], target: ModelID, data_frame: bool ) -> dict[str, Any]: """Convert parameters into a keyword-argument dictionary. Args: - ctds_parameter: list of CallbackTriggerDicts for vm.Parameter. + ctd_parameters: list of CallbackTriggerDicts for vm.Parameter. target: id of targeted figure. data_frame: whether to return only DataFrame parameters starting "data_frame." or only non-DataFrame parameters. @@ -185,7 +185,7 @@ def _get_parametrized_config( config = deepcopy(model_manager[target].figure._arguments) del config["data_frame"] - for ctd in ctds_parameter: + for ctd in ctd_parameters: # TODO: needs to be refactored so that it is independent of implementation details parameter_value = ctd["value"] @@ -221,7 +221,7 @@ def _apply_filters( # Takes in just one target, so dataframe is filtered repeatedly for every target that uses it. # Potentially this could be de-duplicated but it's not so important since filtering is a relatively fast # operation (compared to data loading). - filtered_data = _apply_filter_controls(data_frame=data, ctds_filter=ctds_filter, target=target) + filtered_data = _apply_filter_controls(data_frame=data, ctds_filters=ctds_filter, target=target) filtered_data = _apply_filter_interaction( data_frame=filtered_data, ctds_filter_interaction=ctds_filter_interaction, target=target ) @@ -229,17 +229,17 @@ def _apply_filters( def _get_unfiltered_data( - ctds_parameter: list[CallbackTriggerDict], targets: list[ModelID] + ctds_parameters: list[CallbackTriggerDict], targets: list[ModelID] ) -> dict[ModelID, pd.DataFrame]: # Takes in multiple targets to ensure that data can be loaded efficiently using _multi_load and not repeated for # every single target. - # Getting unfiltered data requires data frame parameters. We pass in all ctds_parameter and then find the + # Getting unfiltered data requires data frame parameters. We pass in all ctd_parameters and then find the # data_frame ones by passing data_frame=True in the call to _get_paramaterized_config. Static data is also # handled here and will just have empty dictionary for its kwargs. multi_data_source_name_load_kwargs: list[tuple[DataSourceName, dict[str, Any]]] = [] for target in targets: dynamic_data_load_params = _get_parametrized_config( - ctds_parameter=ctds_parameter, target=target, data_frame=True + ctd_parameters=ctds_parameters, target=target, data_frame=True ) data_source_name = model_manager[target]["data_frame"] multi_data_source_name_load_kwargs.append((data_source_name, dynamic_data_load_params["data_frame"])) @@ -250,45 +250,25 @@ def _get_unfiltered_data( def _get_modified_page_figures( ctds_filter: list[CallbackTriggerDict], ctds_filter_interaction: list[dict[str, CallbackTriggerDict]], - ctds_parameter: list[CallbackTriggerDict], + ctds_parameters: list[CallbackTriggerDict], targets: list[ModelID], ) -> dict[ModelID, Any]: - from vizro.models import Filter - outputs: dict[ModelID, Any] = {} - control_targets = [] - figure_targets = [] - for target in targets: - if isinstance(model_manager[target], Filter): - control_targets.append(target) - else: - figure_targets.append(target) - - # TODO-NEXT: Add fetching unfiltered data for the Filter.targets as well, once dynamic filters become "targetable" - # from other actions too. For example, in future, if Parameter is targeting only a single Filter. - # Currently, it only works for the on_page_load because Filter.targets are indeed the part of the actions' targets. - # More about the limitation: https://github.com/mckinsey/vizro/pull/879/files#r1863535516 - target_to_data_frame = _get_unfiltered_data(ctds_parameter=ctds_parameter, targets=figure_targets) - # TODO: the structure here would be nicer if we could get just the ctds for a single target at one time, # so you could do apply_filters on a target a pass only the ctds relevant for that target. # Consider restructuring ctds to a more convenient form to make this possible. - for target, unfiltered_data in target_to_data_frame.items(): + + for target, unfiltered_data in _get_unfiltered_data(ctds_parameters, targets).items(): filtered_data = _apply_filters(unfiltered_data, ctds_filter, ctds_filter_interaction, target) outputs[target] = model_manager[target]( data_frame=filtered_data, - **_get_parametrized_config(ctds_parameter=ctds_parameter, target=target, data_frame=False), + **_get_parametrized_config(ctd_parameters=ctds_parameters, target=target, data_frame=False), ) - for target in control_targets: - ctd_filter = [item for item in ctds_filter if item["id"] == model_manager[target].selector.id] - - # This only covers the case of cross-page actions when Filter in an output, but is not an input of the action. - current_value = ctd_filter[0]["value"] if ctd_filter else None - - # target_to_data_frame contains all targets, including some which might not be relevant for the filter in - # question. We filter to use just the relevant targets in Filter.__call__. - outputs[target] = model_manager[target](target_to_data_frame=target_to_data_frame, current_value=current_value) + # TODO NEXT: will need to pass unfiltered_data into Filter.__call__. + # This dictionary is filtered for correct targets already selected in Filter.__call__ or that could be done here + # instead. + # {target: data_frame for target, data_frame in unfiltered_data.items() if target in self.targets} return outputs diff --git a/vizro-core/src/vizro/actions/_filter_action.py b/vizro-core/src/vizro/actions/_filter_action.py index d50f0125c..f3ec21b37 100644 --- a/vizro-core/src/vizro/actions/_filter_action.py +++ b/vizro-core/src/vizro/actions/_filter_action.py @@ -32,6 +32,6 @@ def _filter( return _get_modified_page_figures( ctds_filter=ctx.args_grouping["external"]["filters"], ctds_filter_interaction=ctx.args_grouping["external"]["filter_interaction"], - ctds_parameter=ctx.args_grouping["external"]["parameters"], + ctds_parameters=ctx.args_grouping["external"]["parameters"], targets=targets, ) diff --git a/vizro-core/src/vizro/actions/_on_page_load_action.py b/vizro-core/src/vizro/actions/_on_page_load_action.py index c6611fbd5..306ed9b5e 100644 --- a/vizro-core/src/vizro/actions/_on_page_load_action.py +++ b/vizro-core/src/vizro/actions/_on_page_load_action.py @@ -25,6 +25,6 @@ def _on_page_load(targets: list[ModelID], **inputs: dict[str, Any]) -> dict[Mode return _get_modified_page_figures( ctds_filter=ctx.args_grouping["external"]["filters"], ctds_filter_interaction=ctx.args_grouping["external"]["filter_interaction"], - ctds_parameter=ctx.args_grouping["external"]["parameters"], + ctds_parameters=ctx.args_grouping["external"]["parameters"], targets=targets, ) diff --git a/vizro-core/src/vizro/actions/_parameter_action.py b/vizro-core/src/vizro/actions/_parameter_action.py index bfc58014f..6284481ec 100644 --- a/vizro-core/src/vizro/actions/_parameter_action.py +++ b/vizro-core/src/vizro/actions/_parameter_action.py @@ -27,6 +27,6 @@ def _parameter(targets: list[str], **inputs: dict[str, Any]) -> dict[ModelID, An return _get_modified_page_figures( ctds_filter=ctx.args_grouping["external"]["filters"], ctds_filter_interaction=ctx.args_grouping["external"]["filter_interaction"], - ctds_parameter=ctx.args_grouping["external"]["parameters"], + ctds_parameters=ctx.args_grouping["external"]["parameters"], targets=target_ids, ) diff --git a/vizro-core/src/vizro/actions/filter_interaction_action.py b/vizro-core/src/vizro/actions/filter_interaction_action.py index 9618d265f..bc6659ab9 100644 --- a/vizro-core/src/vizro/actions/filter_interaction_action.py +++ b/vizro-core/src/vizro/actions/filter_interaction_action.py @@ -31,6 +31,6 @@ def filter_interaction(targets: Optional[list[ModelID]] = None, **inputs: dict[s return _get_modified_page_figures( ctds_filter=ctx.args_grouping["external"]["filters"], ctds_filter_interaction=ctx.args_grouping["external"]["filter_interaction"], - ctds_parameter=ctx.args_grouping["external"]["parameters"], + ctds_parameters=ctx.args_grouping["external"]["parameters"], targets=targets or [], ) diff --git a/vizro-core/src/vizro/models/_components/form/_form_utils.py b/vizro-core/src/vizro/models/_components/form/_form_utils.py index 14a20a169..18e666882 100644 --- a/vizro-core/src/vizro/models/_components/form/_form_utils.py +++ b/vizro-core/src/vizro/models/_components/form/_form_utils.py @@ -54,9 +54,6 @@ def validate_value(cls, value, values): [entry["value"] for entry in values["options"]] if isinstance(values["options"][0], dict) else values["options"] ) - if hasattr(value, "__iter__") and ALL_OPTION in value: - return value - if value and not is_value_contained(value, possible_values): raise ValueError("Please provide a valid value from `options`.") diff --git a/vizro-core/src/vizro/models/_components/form/checklist.py b/vizro-core/src/vizro/models/_components/form/checklist.py index ed746dec3..68cb26ad1 100644 --- a/vizro-core/src/vizro/models/_components/form/checklist.py +++ b/vizro-core/src/vizro/models/_components/form/checklist.py @@ -38,8 +38,6 @@ class Checklist(VizroBaseModel): title: str = Field("", description="Title to be displayed") actions: list[Action] = [] - _dynamic: bool = PrivateAttr(False) - # Component properties for actions and interactions _input_property: str = PrivateAttr("value") @@ -48,8 +46,9 @@ class Checklist(VizroBaseModel): _validate_options = root_validator(allow_reuse=True, pre=True)(validate_options_dict) _validate_value = validator("value", allow_reuse=True, always=True)(validate_value) - def __call__(self, options): - full_options, default_value = get_options_and_default(options=options, multi=True) + @_log_call + def build(self): + full_options, default_value = get_options_and_default(options=self.options, multi=True) return html.Fieldset( children=[ @@ -63,13 +62,3 @@ def __call__(self, options): ), ] ) - - def _build_dynamic_placeholder(self): - if self.value is None: - self.value = [get_options_and_default(self.options, multi=True)[1]] - - return self.__call__(self.options) - - @_log_call - def build(self): - return self._build_dynamic_placeholder() if self._dynamic else self.__call__(self.options) diff --git a/vizro-core/src/vizro/models/_components/form/dropdown.py b/vizro-core/src/vizro/models/_components/form/dropdown.py index a56c13c47..d0fa24444 100755 --- a/vizro-core/src/vizro/models/_components/form/dropdown.py +++ b/vizro-core/src/vizro/models/_components/form/dropdown.py @@ -10,7 +10,6 @@ from pydantic import Field, PrivateAttr, StrictBool, root_validator, validator import dash_bootstrap_components as dbc -import dash_mantine_components as dmc from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory @@ -66,10 +65,6 @@ class Dropdown(VizroBaseModel): title: str = Field("", description="Title to be displayed") actions: list[Action] = [] - # Consider making the _dynamic public later. The same property could also be used for all other components. - # For example: vm.Graph could have a dynamic that is by default set on True. - _dynamic: bool = PrivateAttr(False) - # Component properties for actions and interactions _input_property: str = PrivateAttr("value") @@ -87,8 +82,9 @@ def validate_multi(cls, multi, values): raise ValueError("Please set multi=True if providing a list of default values.") return multi - def __call__(self, options): - full_options, default_value = get_options_and_default(options=options, multi=self.multi) + @_log_call + def build(self): + full_options, default_value = get_options_and_default(options=self.options, multi=self.multi) option_height = _calculate_option_height(full_options) return html.Div( @@ -99,35 +95,9 @@ def __call__(self, options): options=full_options, value=self.value if self.value is not None else default_value, multi=self.multi, - optionHeight=option_height, - persistence=True, - persistence_type="session", - ), - ] - ) - - def _build_dynamic_placeholder(self): - # Setting self.value is kind of Dropdown pre_build method. It sets self.value only the first time if it's None. - # We cannot create pre_build for the Dropdown because it has to be called after vm.Filter.pre_build, but nothing - # guarantees that. We can call Filter.selector.pre_build() from the Filter.pre_build() method if we decide that. - # TODO: move this to pre_build once we have better control of the ordering. - if self.value is None: - _, default_value = get_options_and_default(self.options, self.multi) - self.value = default_value - - # TODO-NEXT: Replace this with the "universal Vizro placeholder" component. - return html.Div( - children=[ - html.Legend(children=self.title, className="form-label") if self.title else None, - dmc.DateRangePicker( - id=self.id, - value=self.value, persistence=True, + optionHeight=option_height, persistence_type="session", ), ] ) - - @_log_call - def build(self): - return self._build_dynamic_placeholder() if self._dynamic else self.__call__(self.options) diff --git a/vizro-core/src/vizro/models/_components/form/radio_items.py b/vizro-core/src/vizro/models/_components/form/radio_items.py index 48b8bc6bc..dfa282126 100644 --- a/vizro-core/src/vizro/models/_components/form/radio_items.py +++ b/vizro-core/src/vizro/models/_components/form/radio_items.py @@ -39,8 +39,6 @@ class RadioItems(VizroBaseModel): title: str = Field("", description="Title to be displayed") actions: list[Action] = [] - _dynamic: bool = PrivateAttr(False) - # Component properties for actions and interactions _input_property: str = PrivateAttr("value") @@ -49,8 +47,9 @@ class RadioItems(VizroBaseModel): _validate_options = root_validator(allow_reuse=True, pre=True)(validate_options_dict) _validate_value = validator("value", allow_reuse=True, always=True)(validate_value) - def __call__(self, options): - full_options, default_value = get_options_and_default(options=options, multi=False) + @_log_call + def build(self): + full_options, default_value = get_options_and_default(options=self.options, multi=False) return html.Fieldset( children=[ @@ -64,13 +63,3 @@ def __call__(self, options): ), ] ) - - def _build_dynamic_placeholder(self): - if self.value is None: - self.value = get_options_and_default(self.options, multi=False)[1] - - return self.__call__(self.options) - - @_log_call - def build(self): - return self._build_dynamic_placeholder() if self._dynamic else self.__call__(self.options) 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 a96521708..16f0cb8c9 100644 --- a/vizro-core/src/vizro/models/_components/form/range_slider.py +++ b/vizro-core/src/vizro/models/_components/form/range_slider.py @@ -50,8 +50,6 @@ class RangeSlider(VizroBaseModel): title: str = Field("", description="Title to be displayed.") actions: list[Action] = [] - _dynamic: bool = PrivateAttr(False) - # Component properties for actions and interactions _input_property: str = PrivateAttr("value") @@ -62,7 +60,10 @@ class RangeSlider(VizroBaseModel): _set_default_marks = validator("marks", allow_reuse=True, always=True)(set_default_marks) _set_actions = _action_validator_factory("value") - def __call__(self, min, max, current_value): + @_log_call + def build(self): + init_value = self.value or [self.min, self.max] # type: ignore[list-item] + output = [ Output(f"{self.id}_start_value", "value"), Output(f"{self.id}_end_value", "value"), @@ -85,7 +86,7 @@ def __call__(self, min, max, current_value): return html.Div( children=[ - dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": min, "max": max}), + dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": self.min, "max": self.max}), html.Div( children=[ dbc.Label(children=self.title, html_for=self.id) if self.title else None, @@ -95,10 +96,10 @@ def __call__(self, min, max, current_value): id=f"{self.id}_start_value", type="number", placeholder="min", - min=min, - max=max, + min=self.min, + max=self.max, step=self.step, - value=current_value[0], + value=init_value[0], persistence=True, persistence_type="session", className="slider-text-input-field", @@ -108,15 +109,15 @@ def __call__(self, min, max, current_value): id=f"{self.id}_end_value", type="number", placeholder="max", - min=min, - max=max, + min=self.min, + max=self.max, step=self.step, - value=current_value[1], + value=init_value[1], persistence=True, persistence_type="session", className="slider-text-input-field", ), - dcc.Store(id=f"{self.id}_input_store", storage_type="session"), + dcc.Store(id=f"{self.id}_input_store", storage_type="session", data=init_value), ], className="slider-text-input-container", ), @@ -125,26 +126,14 @@ def __call__(self, min, max, current_value): ), dcc.RangeSlider( id=self.id, - min=min, - max=max, + min=self.min, + max=self.max, step=self.step, marks=self.marks, - value=current_value, + value=init_value, persistence=True, persistence_type="session", className="slider-track-without-marks" if self.marks is None else "slider-track-with-marks", ), ] ) - - def _build_dynamic_placeholder(self, current_value): - return self.__call__(self.min, self.max, current_value) - - @_log_call - def build(self): - current_value = self.value or [self.min, self.max] # type: ignore[list-item] - return ( - self._build_dynamic_placeholder(current_value) - if self._dynamic - else self.__call__(self.min, self.max, current_value) - ) diff --git a/vizro-core/src/vizro/models/_components/form/slider.py b/vizro-core/src/vizro/models/_components/form/slider.py index 2ffdb9f6a..65b37fe9a 100644 --- a/vizro-core/src/vizro/models/_components/form/slider.py +++ b/vizro-core/src/vizro/models/_components/form/slider.py @@ -48,8 +48,6 @@ class Slider(VizroBaseModel): title: str = Field("", description="Title to be displayed.") actions: list[Action] = [] - _dynamic: bool = PrivateAttr(False) - # Component properties for actions and interactions _input_property: str = PrivateAttr("value") @@ -60,7 +58,10 @@ class Slider(VizroBaseModel): _set_default_marks = validator("marks", allow_reuse=True, always=True)(set_default_marks) _set_actions = _action_validator_factory("value") - def __call__(self, min, max, current_value): + @_log_call + def build(self): + init_value = self.value or self.min + output = [ Output(f"{self.id}_end_value", "value"), Output(self.id, "value"), @@ -81,7 +82,7 @@ def __call__(self, min, max, current_value): return html.Div( children=[ - dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": min, "max": max}), + dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": self.min, "max": self.max}), html.Div( children=[ dbc.Label(children=self.title, html_for=self.id) if self.title else None, @@ -91,15 +92,15 @@ def __call__(self, min, max, current_value): id=f"{self.id}_end_value", type="number", placeholder="max", - min=min, - max=max, + min=self.min, + max=self.max, step=self.step, - value=current_value, + value=init_value, persistence=True, persistence_type="session", className="slider-text-input-field", ), - dcc.Store(id=f"{self.id}_input_store", storage_type="session"), + dcc.Store(id=f"{self.id}_input_store", storage_type="session", data=init_value), ], className="slider-text-input-container", ), @@ -108,11 +109,11 @@ def __call__(self, min, max, current_value): ), dcc.Slider( id=self.id, - min=min, - max=max, + min=self.min, + max=self.max, step=self.step, marks=self.marks, - value=current_value, + value=init_value, included=False, persistence=True, persistence_type="session", @@ -120,15 +121,3 @@ def __call__(self, min, max, current_value): ), ] ) - - def _build_dynamic_placeholder(self, current_value): - return self.__call__(self.min, self.max, current_value) - - @_log_call - def build(self): - current_value = self.value if self.value is not None else self.min - return ( - self._build_dynamic_placeholder(current_value) - if self._dynamic - else self.__call__(self.min, self.max, current_value) - ) diff --git a/vizro-core/src/vizro/models/_controls/parameter.py b/vizro-core/src/vizro/models/_controls/parameter.py index cdc76936c..c77c7c7bf 100644 --- a/vizro-core/src/vizro/models/_controls/parameter.py +++ b/vizro-core/src/vizro/models/_controls/parameter.py @@ -56,7 +56,6 @@ def check_data_frame_as_target_argument(cls, target): f"Invalid target {target}. 'data_frame' target must be supplied in the form " ".data_frame." ) - # TODO: Add validation: Make sure the target data_frame is _DynamicData. return target @validator("targets") diff --git a/vizro-core/src/vizro/models/_dashboard.py b/vizro-core/src/vizro/models/_dashboard.py index 2fd7c7b00..b635b8e95 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -308,31 +308,18 @@ def _make_page_404_layout(self): return html.Div( [ # Theme switch is added such that the 404 page has the same theme as the user-selected one. - html.Div( - children=dbc.Switch( - id="theme-selector", - value=self.theme == "vizro_light", - persistence=True, - persistence_type="session", - ), - id="settings", + dbc.Switch( + id="theme-selector", + value=self.theme == "vizro_light", + persistence=True, + persistence_type="session", ), html.Img(src=f"data:image/svg+xml;base64,{error_404_svg}"), - html.Div( - [ - html.Div( - children=[ - html.H3("This page could not be found.", className="heading-3-600"), - html.P("Make sure the URL you entered is correct."), - ], - className="error-text-container", - ), - dbc.Button(children="Take me home", href=get_relative_path("/")), - ], - className="error-content-container", - ), + html.H3("This page could not be found."), + html.P("Make sure the URL you entered is correct."), + dbc.Button(children="Take me home", href=get_relative_path("/"), className="mt-4"), ], - className="page-error-container", + className="d-flex flex-column align-items-center justify-content-center min-vh-100", ) @staticmethod diff --git a/vizro-core/src/vizro/static/css/layout.css b/vizro-core/src/vizro/static/css/layout.css index 996c1108c..a7c66f864 100644 --- a/vizro-core/src/vizro/static/css/layout.css +++ b/vizro-core/src/vizro/static/css/layout.css @@ -85,31 +85,6 @@ border-bottom: 1px solid var(--border-subtleAlpha01); } -.page-error-container { - align-items: center; - display: flex; - flex-direction: column; - height: 100vh; - justify-content: center; - width: 100vw; -} - -.error-content-container { - align-items: center; - display: inline-flex; - flex-direction: column; - gap: 24px; - margin-top: -32px; -} - -.error-text-container { - display: flex; - flex-direction: column; - gap: 8px; - text-align: center; - width: 336px; -} - .dashboard_title { display: flex; flex-direction: column; diff --git a/vizro-core/src/vizro/static/js/models/range_slider.js b/vizro-core/src/vizro/static/js/models/range_slider.js index 8aafdde7f..5eb1892aa 100644 --- a/vizro-core/src/vizro/static/js/models/range_slider.js +++ b/vizro-core/src/vizro/static/js/models/range_slider.js @@ -17,8 +17,6 @@ function update_range_slider_values( trigger_id = dash_clientside.callback_context.triggered[0]["prop_id"].split(".")[0]; } - - // text form component is the trigger if ( trigger_id === `${self_data["id"]}_start_value` || trigger_id === `${self_data["id"]}_end_value` @@ -26,36 +24,21 @@ function update_range_slider_values( if (isNaN(start) || isNaN(end)) { return dash_clientside.no_update; } - return [start, end, [start, end], [start, end]]; - - // slider component is the trigger + [start_text_value, end_text_value] = [start, end]; } else if (trigger_id === self_data["id"]) { - return [slider[0], slider[1], slider, slider]; - } - // on_page_load is the trigger - if (input_store === null) { - return [ - dash_clientside.no_update, - dash_clientside.no_update, - dash_clientside.no_update, - slider, - ]; - } - if ( - slider[0] === start && - input_store[0] === start && - slider[1] === end && - input_store[1] === end - ) { - // To prevent filter_action to be triggered after on_page_load - return [ - dash_clientside.no_update, - dash_clientside.no_update, - dash_clientside.no_update, - dash_clientside.no_update, - ]; + [start_text_value, end_text_value] = [slider[0], slider[1]]; + } else { + [start_text_value, end_text_value] = + input_store !== null ? input_store : [slider[0], slider[1]]; } - return [input_store[0], input_store[1], input_store, input_store]; + + start_value = Math.min(start_text_value, end_text_value); + end_value = Math.max(start_text_value, end_text_value); + start_value = Math.max(self_data["min"], start_value); + end_value = Math.min(self_data["max"], end_value); + slider_value = [start_value, end_value]; + + return [start_value, end_value, slider_value, [start_value, end_value]]; } window.dash_clientside = { diff --git a/vizro-core/src/vizro/static/js/models/slider.js b/vizro-core/src/vizro/static/js/models/slider.js index 1b15d78ae..bc572cffe 100644 --- a/vizro-core/src/vizro/static/js/models/slider.js +++ b/vizro-core/src/vizro/static/js/models/slider.js @@ -6,31 +6,20 @@ function update_slider_values(start, slider, input_store, self_data) { trigger_id = dash_clientside.callback_context.triggered[0]["prop_id"].split(".")[0]; } - - // text form component is the trigger if (trigger_id === `${self_data["id"]}_end_value`) { if (isNaN(start)) { return dash_clientside.no_update; } - return [start, start, start]; - - // slider component is the trigger + end_value = start; } else if (trigger_id === self_data["id"]) { - return [slider, slider, slider]; - } - // on_page_load is the trigger - if (input_store === null) { - return [dash_clientside.no_update, dash_clientside.no_update, slider]; + end_value = slider; + } else { + end_value = input_store !== null ? input_store : self_data["min"]; } - if (slider === start && start === input_store) { - // To prevent filter_action to be triggered after on_page_load - return [ - dash_clientside.no_update, - dash_clientside.no_update, - dash_clientside.no_update, - ]; - } - return [input_store, input_store, input_store]; + + end_value = Math.min(Math.max(self_data["min"], end_value), self_data["max"]); + + return [end_value, end_value, end_value]; } window.dash_clientside = {