diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21bc464cf..e85739768 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -112,4 +112,3 @@ ci: - codespell - bandit - mypy - diff --git a/vizro-core/changelog.d/20241202_150655_huong_li_nguyen_refactor_bs_example.md b/vizro-core/changelog.d/20241202_150655_huong_li_nguyen_refactor_bs_example.md index 7c0d58d4f..4abc0f11e 100644 --- a/vizro-core/changelog.d/20241202_150655_huong_li_nguyen_refactor_bs_example.md +++ b/vizro-core/changelog.d/20241202_150655_huong_li_nguyen_refactor_bs_example.md @@ -10,36 +10,42 @@ Uncomment the section that is right (remove the HTML comment wrapper). - 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)) --> + + + + + + + In a development environment the easiest way to enable caching is to use a [simple memory cache](https://cachelib.readthedocs.io/en/stable/simple/) with the default configuration options. This is achieved by adding one line to the above example to set `data_manager.cache`: !!! example "Simple cache with default timeout of 5 minutes" - ```py hl_lines="13" from flask_caching import Cache from vizro import Vizro @@ -225,7 +222,6 @@ data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_T ``` !!! warning - Simple cache exists purely for single-process development purposes and is not intended to be used in production. If you deploy with multiple workers, [for example with Gunicorn](run.md/#gunicorn), then you should use a production-ready cache backend. All of Flask-Caching's [built-in backends](https://flask-caching.readthedocs.io/en/latest/#built-in-cache-backends) other than `SimpleCache` are suitable for production. In particular, you might like to use [`FileSystemCache`](https://cachelib.readthedocs.io/en/stable/file/) or [`RedisCache`](https://cachelib.readthedocs.io/en/stable/redis/): ```py title="Production-ready caches" @@ -239,7 +235,9 @@ data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_T Since Flask-Caching relies on [`pickle`](https://docs.python.org/3/library/pickle.html), which can execute arbitrary code during unpickling, you should not cache data from untrusted sources. Doing so [could be unsafe](https://github.com/pallets-eco/flask-caching/pull/209). Note that when a production-ready cache backend is used, the cache is persisted beyond the Vizro process and is not cleared by restarting your server. To clear the cache then you must do so manually, for example, if you use `FileSystemCache` then you would delete your `cache` directory. Persisting the cache can also be useful for development purposes when handling data that takes a long time to load: even if you do not need the data to refresh while your dashboard is running, it can speed up your development loop to use dynamic data with a cache that is persisted between repeated runs of Vizro. + + #### Set timeouts You can change the timeout of the cache independently for each dynamic data source in the data manager using the `timeout` setting (measured in seconds). A `timeout` of 0 indicates that the cache does not expire. This is effectively the same as using [static data](#static-data). @@ -278,8 +276,8 @@ In general, a parametrized dynamic data source should always return a pandas Dat To add a parameter to control a dynamic data source, do the following: 1. add the appropriate argument to your dynamic data function and specify a default value for the argument. -2. give an `id` to all components that have the data source you wish to alter through a parameter. -3. [add a parameter](parameters.md) with `targets` of the form `.data_frame.` and a suitable [selector](selectors.md). +1. give an `id` to all components that have the data source you wish to alter through a parameter. +1. [add a parameter](parameters.md) with `targets` of the form `.data_frame.` and a suitable [selector](selectors.md). For example, let us extend the [dynamic data example](#dynamic-data) above into an example of how parametrized dynamic data works. The `load_iris_data` can take an argument `number_of_points` controlled from the dashboard with a [`Slider`][vizro.models.Slider]. @@ -318,21 +316,18 @@ For example, let us extend the [dynamic data example](#dynamic-data) above into ``` 1. `load_iris_data` takes a single argument, `number_of_points`, with a default value of 10. - 2. `iris` is a pandas DataFrame created by reading from the CSV file `iris.csv`. - 3. Sample points at random, where `number_of_points` gives the number of points selected. - 4. To use `load_iris_data` as dynamic data it must be added to the data manager. You should **not** actually call the function as `load_iris_data()` or `load_iris_data(number_of_points=...)`; doing so would result in static data that cannot be reloaded. - 5. Give the `vm.Graph` component `id="graph"` so that the `vm.Parameter` can target it. Dynamic data is referenced by the name of the data source `"iris"`. - 6. Create a `vm.Parameter` to target the `number_of_points` argument for the `data_frame` used in `graph`. + 1. `iris` is a pandas DataFrame created by reading from the CSV file `iris.csv`. + 1. Sample points at random, where `number_of_points` gives the number of points selected. + 1. To use `load_iris_data` as dynamic data it must be added to the data manager. You should **not** actually call the function as `load_iris_data()` or `load_iris_data(number_of_points=...)`; doing so would result in static data that cannot be reloaded. + 1. Give the `vm.Graph` component `id="graph"` so that the `vm.Parameter` can target it. Dynamic data is referenced by the name of the data source `"iris"`. + 1. Create a `vm.Parameter` to target the `number_of_points` argument for the `data_frame` used in `graph`. === "Result" - [![ParametrizedDynamicData]][ParametrizedDynamicData] - - [ParametrizedDynamicData]: ../../assets/user_guides/data/parametrized_dynamic_data.gif + [![ParametrizedDynamicData]][parametrizeddynamicdata] Parametrized data loading is compatible with [caching](#configure-cache). The cache uses [memoization](https://flask-caching.readthedocs.io/en/latest/#memoization), so that the dynamic data function's arguments are included in the cache key. This means that `load_iris_data(number_of_points=10)` is cached independently of `load_iris_data(number_of_points=20)`. !!! warning - You should always [treat the content of user input as untrusted](https://community.plotly.com/t/writing-secure-dash-apps-community-thread/54619). For example, you should not expose a filepath to load without passing it through a function like [`werkzeug.utils.secure_filename`](https://werkzeug.palletsprojects.com/en/3.0.x/utils/#werkzeug.utils.secure_filename), or you might enable arbitrary access to files on your server. You cannot pass [nested parameters](parameters.md#nested-parameters) to dynamic data. You can only target the top-level arguments of the data loading function, not the nested keys in a dictionary. @@ -354,7 +349,6 @@ When the page is refreshed, the behavior of a dynamic filter is as follows: For example, let us add two filters to the [dynamic data example](#dynamic-data) above: !!! example "Dynamic filters" - ```py hl_lines="10 20 21" from vizro import Vizro import pandas as pd @@ -386,8 +380,8 @@ For example, let us add two filters to the [dynamic data example](#dynamic-data) ``` 1. We sample only 5 rather than 50 points so that changes to the available values in the filtered columns are more apparent when the page is refreshed. - 2. This filter implicitly controls the dynamic data source `"iris"`, which supplies the `data_frame` to the targeted `vm.Graph`. On page refresh, Vizro reloads this data, finds all the unique values in the `"species"` column and sets the categorical selector's `options` accordingly. - 3. Similarly, on page refresh, Vizro finds the minimum and maximum values of the `"sepal_length"` column in the reloaded data and sets new `min` and `max` values for the numerical selector accordingly. + 1. This filter implicitly controls the dynamic data source `"iris"`, which supplies the `data_frame` to the targeted `vm.Graph`. On page refresh, Vizro reloads this data, finds all the unique values in the `"species"` column and sets the categorical selector's `options` accordingly. + 1. Similarly, on page refresh, Vizro finds the minimum and maximum values of the `"sepal_length"` column in the reloaded data and sets new `min` and `max` values for the numerical selector accordingly. Consider a filter that depends on dynamic data, where you do **not** want the available values to change when the dynamic data changes. You should manually specify the `selector`'s `options` field (categorical selector) or `min` and `max` fields (numerical selector). In the above example, this could be achieved as follows: @@ -409,10 +403,13 @@ controls = [ When Vizro initially builds a filter that depends on parametrized dynamic data loading, data is loaded using the default argument values. This data is used to: -* perform initial validation -* check which data sources contain the specified `column` (unless `targets` is explicitly specified) and -* find the type of selector to use (unless `selector` is explicitly specified). +- perform initial validation +- check which data sources contain the specified `column` (unless `targets` is explicitly specified) and +- find the type of selector to use (unless `selector` is explicitly specified). !!! note - When the value of a dynamic data parameter is changed by a dashboard user, the data underlying a dynamic filter can change. Currently this change affects page components such as `vm.Graph` but does not affect the available values shown in a dynamic filter, which only update on page refresh. This functionality will be coming soon! + +[databasic]: ../../assets/user_guides/data/data_pandas_dataframe.png +[dynamicdata]: ../../assets/user_guides/data/dynamic_data.gif +[parametrizeddynamicdata]: ../../assets/user_guides/data/parametrized_dynamic_data.gif diff --git a/vizro-core/docs/pages/user-guides/filters.md b/vizro-core/docs/pages/user-guides/filters.md index bc3649178..8228ecbe7 100644 --- a/vizro-core/docs/pages/user-guides/filters.md +++ b/vizro-core/docs/pages/user-guides/filters.md @@ -2,8 +2,7 @@ This guide shows you how to add filters to your dashboard. One main way to interact with the charts/components on your page is by filtering the underlying data. A filter selects a subset of rows of a component's underlying DataFrame which alters the appearance of that component on the page. -The [`Page`][vizro.models.Page] model accepts the `controls` argument, where you can enter a [`Filter`][vizro.models.Filter] model. -This model enables the automatic creation of [selectors](selectors.md) (for example, `Dropdown` or `RangeSlider`) that operate on the charts/components on the screen. +The [`Page`][vizro.models.Page] model accepts the `controls` argument, where you can enter a [`Filter`][vizro.models.Filter] model. This model enables the automatic creation of [selectors](selectors.md) (for example, `Dropdown` or `RangeSlider`) that operate on the charts/components on the screen. By default, filters that control components with [dynamic data](data.md#dynamic-data) are [dynamically updated](data.md#filters) when the underlying data changes while the dashboard is running. @@ -12,7 +11,7 @@ By default, filters that control components with [dynamic data](data.md#dynamic- To add a filter to your page, do the following: 1. add the [`Filter`][vizro.models.Filter] model into the `controls` argument of the [`Page`][vizro.models.Page] model -2. configure the `column` argument, which denotes the target column to be filtered +1. configure the `column` argument, which denotes the target column to be filtered You can also set `targets` to specify which components on the page the filter should apply to. If this is not explicitly set then `targets` defaults to all components on the page whose data source includes `column`. @@ -39,6 +38,7 @@ You can also set `targets` to specify which components on the page the filter sh Vizro().build(dashboard).run() ``` + === "app.yaml" ```yaml # Still requires a .py to add data to the data manager and parse YAML configuration @@ -57,17 +57,15 @@ You can also set `targets` to specify which components on the page the filter sh type: filter title: My first page ``` - === "Result" - [![Filter]][Filter] - - [Filter]: ../../assets/user_guides/control/control1.png + === "Result" + [![Filter]][filter] The selector is configured automatically based on the target column type data as follows: - - Categorical data uses [`vm.Dropdown(multi=True)`][vizro.models.Dropdown] where `options` is the set of unique values found in `column` across all the data sources of components in `targets`. - - [Numerical data](https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_numeric_dtype.html) uses [`vm.RangeSlider`][vizro.models.RangeSlider] where `min` and `max` are the overall minimum and maximum values found in `column` across all the data sources of components in `targets`. - - [Temporal data](https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_datetime64_any_dtype.html) uses [`vm.DatePicker(range=True)`][vizro.models.DatePicker] where `min` and `max` are the overall minimum and maximum values found in `column` across all the data sources of components in `targets`. A column can be converted to this type with [pandas.to_datetime](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html). +- Categorical data uses [`vm.Dropdown(multi=True)`][vizro.models.Dropdown] where `options` is the set of unique values found in `column` across all the data sources of components in `targets`. +- [Numerical data](https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_numeric_dtype.html) uses [`vm.RangeSlider`][vizro.models.RangeSlider] where `min` and `max` are the overall minimum and maximum values found in `column` across all the data sources of components in `targets`. +- [Temporal data](https://pandas.pydata.org/docs/reference/api/pandas.api.types.is_datetime64_any_dtype.html) uses [`vm.DatePicker(range=True)`][vizro.models.DatePicker] where `min` and `max` are the overall minimum and maximum values found in `column` across all the data sources of components in `targets`. A column can be converted to this type with [pandas.to_datetime](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html). The following example demonstrates these default selector types. @@ -107,6 +105,7 @@ The following example demonstrates these default selector types. Vizro().build(dashboard).run() ``` + === "app.yaml" ```yaml # Still requires a .py to add data to the data manager and parse YAML configuration @@ -129,15 +128,13 @@ The following example demonstrates these default selector types. type: filter title: My first page ``` - === "Result" - [![Filter]][Filter] - [Filter]: ../../assets/user_guides/selectors/default_filter_selectors.png + === "Result" + [![Filter]][filter] ## Change selector -If you want to have a different selector for your filter, you can give the `selector` argument of the [`Filter`][vizro.models.Filter] a different selector model. -Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], [`RangeSlider`][vizro.models.RangeSlider], [`Slider`][vizro.models.Slider], and [`DatePicker`][vizro.models.DatePicker]. +If you want to have a different selector for your filter, you can give the `selector` argument of the [`Filter`][vizro.models.Filter] a different selector model. Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], [`RangeSlider`][vizro.models.RangeSlider], [`Slider`][vizro.models.Slider], and [`DatePicker`][vizro.models.DatePicker]. !!! example "Filter with different selector" === "app.py" @@ -162,6 +159,7 @@ Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropd Vizro().build(dashboard).run() ``` + === "app.yaml" ```yaml # Still requires a .py to add data to the data manager and parse YAML configuration @@ -181,11 +179,9 @@ Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropd type: filter title: My first page ``` - === "Result" - - [![Selector]][Selector] - [Selector]: ../../assets/user_guides/control/control2.png + === "Result" + [![Selector]][selector] ## Further customization @@ -220,6 +216,7 @@ Below is an advanced example where we only target one page component, and where Vizro().build(dashboard).run() ``` + === "app.yaml" ```yaml # Still requires a .py to add data to the data manager and parse YAML configuration @@ -244,17 +241,19 @@ Below is an advanced example where we only target one page component, and where controls: - column: petal_length targets: - - scatter_chart + - scatter_chart selector: step: 1 type: range_slider type: filter title: My first page ``` - === "Result" - - [![Advanced]][Advanced] - [Advanced]: ../../assets/user_guides/control/control3.png + === "Result" + [![Advanced]][advanced] To further customize selectors, see our [how-to-guide on creating custom components](custom-components.md). + +[advanced]: ../../assets/user_guides/control/control3.png +[filter]: ../../assets/user_guides/control/control1.png +[selector]: ../../assets/user_guides/control/control2.png