How to create custom figures
This guide explains how to create custom figures, which is useful when you need a component that reacts to filter and parameter controls.
The Figure model accepts the figure argument, where you can enter any custom figure function as explained in the user guide on figures.
When to use a custom figure
As described in the flowchart detailing when to use Figure, custom figures should be used if both of the following conditions are met:
- You need a figure that doesn't fit into the existing built-in models (
Graph,TableorAgGrid). - You need a figure that isn't available in our built-in figure functions
vizro.figures.
Steps to create a custom figure
- Define a function that returns a Dash component. This can, but does not need to, be based on code in our built-in figure functions in
vizro.figures. - Decorate it with
@capture("figure"). - The function must accept a
data_frameargument (of typepandas.DataFrame). - The figure should be derived from and require only one
pandas.DataFrame. Dataframes from other arguments will not react to dashboard controls such asFilter. - Pass your function to the
figureargument of theFiguremodel.
The following examples can be used as a base to build more sophisticated figures.
Examples of custom figures
Custom KPI card
To change the design or content of our existing KPI (key performance indicator) cards from vizro.figures, you can do so by following the steps described above.
For instance, to make a KPI card with the icon positioned on the right side of the title instead of the left, copy and paste the source code of kpi_card and adjust the return statement of the function.
Custom KPI card
import dash_bootstrap_components as dbc
import pandas as pd
import vizro.models as vm
import vizro.plotly.express as px
from dash import html
from vizro import Vizro
from vizro.figures import kpi_card
from vizro.models.types import capture
tips = px.data.tips()
@capture("figure") # (1)!
def custom_kpi_card(
data_frame: pd.DataFrame,
value_column: str,
*,
value_format: str = "{value}",
agg_func: str = "sum",
title: str | None,
icon: str | None,
) -> dbc.Card: # (2)!
title = title or f"{agg_func} {value_column}".title()
value = data_frame[value_column].agg(agg_func)
header = dbc.CardHeader(
[
html.H4(title, className="card-kpi-title"),
html.P(icon, className="material-symbols-outlined") if icon else None, # (3)!
]
)
body = dbc.CardBody([value_format.format(value=value)])
return dbc.Card([header, body], class_name="card-kpi")
page = vm.Page(
title="Create your own KPI card",
layout=vm.Flex(direction="row"), # (4)!
components=[
vm.Figure(
figure=kpi_card( # (5)!
data_frame=tips,
value_column="tip",
value_format="${value:.2f}",
icon="Shopping Cart",
title="Default KPI card",
)
),
vm.Figure(
figure=custom_kpi_card( # (6)!
data_frame=tips,
value_column="tip",
value_format="${value:.2f}",
icon="Payment",
title="Custom KPI card",
)
),
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
Run and edit this code in Py.Cafe
- Here we decorate our custom figure function with the
@capture("figure")decorator. - The custom figure function needs to have a
data_frameargument and return aDashcomponent. - We adjust the return statement to include the icon on the right side of the title. This is achieved by swapping the order of the
html.H2andhtml.Pcompared to the originalkpi_card. - We use a
Flexlayout withdirection="row"to ensure the KPI cards are placed side by side and only take up as much space as needed. - For more information, refer to the API reference for the
kpi_card. - Our custom figure function
custom_kpi_cardnow needs to be passed on to thevm.Figure.
# Still requires a .py to add data to the data manager, define CapturedCallables, and parse YAML configuration
# More explanation in the docs on `Dashboard` and extensions.
pages:
- components:
- figure:
_target_: kpi_card
data_frame: tips
value_column: tip
value_format: ${value:.2f}
icon: Shopping Cart
title: Default KPI card
type: figure
- figure:
_target_: __main__.custom_kpi_card
data_frame: tips
value_column: tip
value_format: ${value:.2f}
icon: Payment
title: Custom KPI card
type: figure
layout:
direction: row
type: flex
title: Create your own KPI card
Dynamic HTML header
You can create a custom figure for any Dash component. Below is an example of a custom figure that returns a html.H2 component that dynamically updates based on the selected name from a filter.
Dynamic HTML header
import pandas as pd
import vizro.models as vm
from dash import html
from vizro import Vizro
from vizro.models.types import capture
df = pd.DataFrame({"names": ["Emma", "Jack", "Sophia", "Ethan", "Mia"]})
@capture("figure") # (1)!
def dynamic_html_header(data_frame: pd.DataFrame, column: str) -> html.H2: # (2)!
"""Creates a HTML header that dynamically updates based on controls."""
return html.H2(f"Good morning, {data_frame[column].iloc[0]}! ☕ ⛅") # (3)!
page = vm.Page(
title="Dynamic HTML header",
components=[vm.Figure(figure=dynamic_html_header(data_frame=df, column="names"))], # (4)!
controls=[vm.Filter(column="names", selector=vm.RadioItems(title="Select a name"))],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
Run and edit this code in Py.Cafe
- Here we decorate our custom figure function with the
@capture("figure")decorator. - The custom figure function needs to have a
data_frameargument and return aDashcomponent. - We return a
html.H2component that dynamically updates based on the selected name from the filter. - Our custom figure function
dynamic_html_headernow needs to be passed on to thevm.Figure.
# Still requires a .py to add data to the data manager, define CapturedCallables, and parse YAML configuration
# More explanation in the docs on `Dashboard` and extensions.
pages:
- components:
- figure:
_target_: __main__.dynamic_html_header
data_frame: df
column: names
type: figure
controls:
- column: names
type: filter
selector:
type: radio_items
title: Dynamic HTML header
Dynamic number of cards
The example below shows how to create multiple cards created from a pandas.DataFrame where the number of cards displayed dynamically adjusts based on a vm.Parameter.
Dynamic number of cards
import dash_bootstrap_components as dbc
import pandas as pd
import vizro.models as vm
from dash import dcc, html
from vizro import Vizro
from vizro.models.types import capture
text = [
"Lorem ipsum dolor sit amet, consetetur sadipscing no sea elitr sed diam nonumy.",
"Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
"Sed diam voluptua. At vero eos et accusam et justo no duo dolores et ea rebum.",
"Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
"Lorem ipsum dolor sit amet, consetetur sadipscing no sea est elitr dolor sit amet.",
"Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
]
df = pd.DataFrame({"text": text * 2})
@capture("figure") # (1)!
def multiple_cards(data_frame: pd.DataFrame, n_rows: int = 1) -> html.Div: # (2)!
"""Creates a list with a variable number of `vm.Card` components from the provided data_frame.
Args:
data_frame: Data frame containing the data.
n_rows: Number of rows to use from the data_frame. Defaults to 1.
Returns:
html.Div with a list of dbc.Card objects generated from the data.
"""
texts = data_frame.head(n_rows)["text"]
return html.Div(
[dbc.Card(dcc.Markdown(f"### Card #{i}\n{text}")) for i, text in enumerate(texts, 1)],
className="multiple-cards-container",
)
page = vm.Page(
title="Page with variable number of cards",
components=[vm.Figure(id="my-figure", figure=multiple_cards(data_frame=df))], # (3)!
controls=[
vm.Parameter(
targets=["my-figure.n_rows"], # (4)!
selector=vm.Slider(min=2, max=12, step=2, value=10, title="Number of cards to display"),
),
],
layout=vm.Flex()
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
- Here we decorate our custom figure function with the
@capture("figure")decorator. - The custom figure function needs to have a
data_frameargument and return aDashcomponent. - Our decorated figure function
multiple_cardsnow needs to be passed on to thevm.Figure. - We add a
vm.Parameterto dynamically adjust the number of cards displayed. The parameter targets then_rowsargument of themultiple_cardsfunction, determining the number of rows taken from the data.
# Still requires a .py to add data to the data manager, define CapturedCallables, and parse YAML configuration
# More explanation in the docs on `Dashboard` and extensions.
pages:
- components:
- figure:
_target_: __main__.multiple_cards
data_frame: df
id: my-figure
type: figure
controls:
- targets: [my-figure.n_rows]
type: parameter
selector:
type: slider
min: 2
max: 12
step: 2
value: 10
title: Number of cards to display
layout:
type: flex
title: Dynamic HTML header


