I am trying to set the title of Vizro app dynamically, using the data i get in a Dash callback on the initial load. How do I actually change the title - is that possible and what’s the recommended approach?
Actually, the question is broader - is there a way to preconfigure something in a Vizro based app to create a context specific dashboard - like tenant in a multi tenant system. With tenant specific values in filters, and components?
Maybe @li.nguyen can help?
Hey @alexe , and thanks for the great question!
In general, Vizro enables quite flexible customisation of the page’s main content, especially by utilising the following features described in Vizro docs: extensions and dynamic data. However, Vizro is a bit stiff when header/footer customisation is needed.
As you already mentioned, to customise the dashboard title in the header you can trigger the Dash callback. For example, you can trigger the callback when you navigate to page so that the dashboard title is adjusted. Here’s the code example below where the Input("_pages_store", "data")
is used. The "_page_store"
is an internal Dash component that contains the page title and will cause the callback to be triggered each time you change or refresh the page.
P.S. You can use Input("_pages_content", "children")
as well. It enables you to access much more data (than just a page title) that’s visible on the screen. You can also use some of aforementioned components as Inputs, to just trigger the callback when the page is opened, but return the content that is based on something else (e.g. f"Show: {len(df)} rows"
)
Here’s the example:
"""Dev app to try things out."""
import pandas as pd
from dash import callback, Input, Output
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
df = px.data.iris()
page_1 = vm.Page(
title="Page 1 title",
components=[
vm.Graph(figure=px.scatter(df, x="sepal_width", y="sepal_length", color="species")),
],
)
page_2 = vm.Page(
title="Page 2 title",
components=[
vm.Graph(figure=px.scatter(df, x="sepal_width", y="sepal_length", color="species")),
],
)
@callback(
Output("dashboard-title", "children"),
Input("_pages_store", "data"),
)
def update_dashboard_title(pages_store):
return pages_store["title"]
dashboard = vm.Dashboard(pages=[page_1, page_2], title="Dashboard ")
if __name__ == "__main__":
Vizro().build(dashboard).run()
About the multi-tenant question: Vizro is a stateless framework, just like Dash. What this means is that by default there’s no such thing as a user/tenant. The code that is called upon HTTP requests to runs filters etc. has no context informing whose machine it came from. The only thing that distinguishes different users is what’s stored clientside in their browser storage. So, there’s unfortunately no native way
to achieve multi-tenant dashboard with Vizro.
The lack of native authentication support in Vizro does not mean that it is not entirely possible to achieve this behaviour. You can try using dash-auth or any other flask auth alternative to support authentication/authorisation and return different content according to user permissions.
Petar, thank you so much for your help and for the great answer—also, that was a really nice, simple, and working example!
I don’t have any experience with Dash and Vizro, and a lot of things still feel a bit too magical. I’m trying to understand how it all works—what can be done and where.
My use case is basically this: an external web application that has user/tenant context invokes a Dash/Vizro app, which:
- Extracts the user from the HTTP request.
- Retrieves the current tenant for the user from a cookie for future use.
- Populates a dropdown in the filter with tenant-specific objects retrieved from a database.
Through trial and error, I was able to get this working in Dash, but I got stuck trying to do the same in Vizro. Is building filter dynamically doable?
Hey @alexe
Is building filter dynamically doable?
Yep it’s possible. Find more about Vizro dynamic filters in the official docs → Data - Vizro
The real question is how to utilise the dynamic Vizro filter but where its options are based on the user’s role.
Here’s the example that returns different filter checklist options based on the user’s role. User role in this example is represented as a string “user”/“admin” and is propagated from the frontend side. However, you can propagate cookie from the frontend side in the similar way and than extract the user’s role on the backend side instead.
Refresh the app a few times to see how the app changes the Checklist listed options based on the “user_role”, because currently the “user_role” is randomly calculated for the purpose of this example.
"""Dev app to try things out."""
import pandas as pd
from dash import clientside_callback, Input, Output
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.managers import data_manager
# A dummy example that returns different dataframes based on the user_role.
# You can propagate cookie from the fronted instead, and then validate it here.
def dynamic_df_load(user_role="user"):
if user_role == "user":
# First 75 rows only containing 2 species: setosa and versicolor
return px.data.iris().head(75)
elif user_role == "admin":
# All rows containing all 3 species: setosa, versicolor, and virginica
return px.data.iris()
# dynamic_df_load function will be called every time the page is refreshed and its output will define:
# 1. the new checklist filter options
# 2. the new data_frame for the graph
# dynamic_df_load input argument "user_role" is the value selected in the "user_role_parameter" component.
# dynamic_df_load output is the data_frame that is different based on the "user_role" parameter input.
# More about dynamic data in Vizro -> https://vizro.readthedocs.io/en/stable/pages/user-guides/data/#dynamic-data
# More about how to parametrise data loading function -> https://vizro.readthedocs.io/en/stable/pages/user-guides/data/#parametrize-data-loading
data_manager["dynamic_df"] = dynamic_df_load
page_1 = vm.Page(
title="Page 1 title",
components=[
vm.Graph(
id="graph_id",
figure=px.scatter("dynamic_df", x="sepal_width", y="sepal_length", color="species")
),
],
controls=[
# This filter will be updated every time the page is refreshed with the new data_frame["species"] unique options.
vm.Filter(
column="species",
selector=vm.Checklist(),
),
# Selected value from this parameter will be propagated to the dynamic_df_load function.
vm.Parameter(
targets=["graph_id.data_frame.user_role"],
selector=vm.RadioItems(
id="user_role_parameter",
options=["user", "admin"],
value="user"
),
)
]
)
# This is a clientside callback that is triggered every time user opens a page BEFORE dynamic_df_load is triggered.
# It propagates user role into the "user_role_parameter" that is then propagate to the dynamic_df_load.
# You can propagate web cookies from the frontend instead,
# and then extract the user's role on the backed side within the dynamic_df_load function.
clientside_callback(
"""
function(pages_store) {
// Add logic to get user role. Or propagate the cookie instead
if (Math.random() > 0.5) {
user_role = "admin";
} else {
user_role = "user";
}
return [`Hello, ${user_role} 👋`, user_role];
}
""",
Output("dashboard-title", "children"),
Output("user_role_parameter", "value"),
Input("_pages_store", "data"),
)
# Add title="Title" so the "dashboard-title" component is created and can be updated by the clientside_callback
dashboard = vm.Dashboard(pages=[page_1], title="Title")
if __name__ == "__main__":
Vizro().build(dashboard).run()
P.S. The “user_info_parameter” is currently visible for the purpose of this example. It should be hidden by adding the following css within the custom.css file, located in the assets folder alongside app.py.
More about custom css in Vizro → Custom CSS - Vizro
→ assets/
------>custom.css
→ app.py
fieldset:has(> #user_info_parameter) {
display: none;
}
Screenshots:
I don’t know what approach did you use to setup the cookies handling in the plain Dash app, but probably that the same should be possible to do in Vizro too. As Dash and thus Vizro are based on Flask, it should be possible to add cookies=flask.request.cookies
within the dynamic_df_load function directly. In that case, you don’t have to use vm.Parameter(…), custom.css and clientside_callback
in the example above.
Your use case looks quite interesting, so please let me know about results I’m sure we will find the proper solution for you.
Hi Petar,
Thank you again for your help and for the great examples. I’m going to adapt them and try to build the app based on your approach, but it may take some time since I’m learning too many things at once—Dash, Vizro, and refreshing Python, which I last used over 20 years ago. Feels like changing tires, putting gas, and learning to drive all at the same time!
I’ll let you know how it goes. Your advice really saved the project—I was about to give up. I truly appreciate it!