Skip to content

Graph and table interactions

In this guide we show you how to configure interactions between graphs and tables, as is commonly done in business intelligence (BI) tools. In Vizro, all such interactions are enabled by an intermediate control that you must explicitly define. For example:

  • Cross-filter: a source graph or table filters the data_frame of a target graph or table. The source component sets a filter, which in turn updates the target component.
  • Cross-parameter: a source graph or table updates any argument other than data_frame of a target graph or table. The source component sets a parameter, which in turn updates the target component.
  • Cross-highlight: a source graph or table highlights data in a target graph or table. This is an example of a cross-parameter.

All these interactions use the set_control action. This gives very generic and powerful functionality thanks to the functionality of the intermediate control:

  • The target components can be anything that reacts to a control: built-in graphs, custom graphs, built-in tables, custom tables, built-in figures and custom figures.
  • A single control can update any number of these target components, and a single source component can set any number of controls. Hence a single source component can interact with any number of target components.
  • A target component can be on the same page as the source or on a different page (so long as the intermediate control has show_in_url=True).
  • A target component can also be the source component to enable a "self-interaction".
  • The value of a control is persisted when you change page.
  • Interactions are not "invisible"; they are explicitly shown on the screen by the value of the control. Just like a normal control, you can change the value manually.

Invisible controls

If you prefer, you can make your control invisible by setting visible=False, for example vm.Parameter(..., visible=False). The control can then only be set by set_control. This achieves a visually cleaner dashboard but can also make it less clear what graph and table interactions have been applied. We use visible=False in all our examples on cross-highlighting.

A user can reset all controls on a page, including those with visible=False, by clicking the "Reset controls" button.

Cross-filter

A cross-filter is when the user clicks on one source graph or table to filter one or more target components. In Vizro, a cross-filter operates through an intermediate filter. To configure a cross-filter:

  1. Create a filter that targets the graphs, tables or figures you would like to filter. The filter must have a categorical selector (both multi- and single-option are allowed).

    import vizro.models as vm
    
    controls = [vm.Filter(id="my_filter", column="species")]  # (1)!
    
    1. Remember that if targets is not explicitly specified, a filter targets all components on the page whose data source includes column.
  2. Call set_control in the actions argument of the source Graph or AgGrid component that triggers the cross-filter.

    1. Set control to the ID of the filter.
    2. Set value. The format of this depends on the source model and is given in the API reference, but it is often column of the filter. Think of it as an instruction for what to lookup in the source data: whatever value is fetched from this lookup is used to set control.
    import vizro.actions as va
    
    components = [vm.Graph(..., actions=va.set_control(control="my_filter", value="species"))]
    
  3. If your source component is a Graph and you use a column name for value then this must be included in the custom_data of your graph's figure function, for example figure=px.scatter(..., custom_data="species").

Tip

Often the value of set_control is the same as the column of the filter, but this does not need to be the case. You can perform a cross-filter where the source component's column name given by value is different from the target component's column name, which is given by the filter's column.

Cross-filter from table

The trigger for a cross-filter from an AG Grid is clicking on a row in the table. The value argument of the set_control action specifies the column of the value that sets control.

Cross-filter from table to graph

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page = vm.Page(
    title="Cross-filter from table to graph",
    components=[
        vm.AgGrid(
            title="Click on a row to use that row's sex to filter graph",
            figure=dash_ag_grid(tips),
            actions=va.set_control(control="sex_filter", value="sex"),
        ),
        vm.Graph(id="tips_graph", figure=px.histogram(tips, x="tip")),  # (1)!
    ],
    controls=[vm.Filter(id="sex_filter", column="sex", targets=["tips_graph"])],  # (2)!
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. We give the vm.Graph an id so that it can be targeted explicitly by vm.Filter(id="sex_filter").
  2. We give the vm.Filter an id so that it can be set explicitly by va.set_control.
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: sex_filter
            type: set_control
            value: sex
        figure:
          _target_: dash_ag_grid
          data_frame: tips
        title: Click on a row to use that row's sex to filter graph
        type: ag_grid
      - figure:
          _target_: histogram
          data_frame: tips
          x: tip
        id: tips_graph
        type: graph
    controls:
      - column: sex
        id: sex_filter
        targets:
          - tips_graph
        type: filter
    title: Cross-filter from table to graph

When you click on a row in the table, the graph is cross-filtered to show data only for one sex. The column of the clicked cell does not matter: the value is always taken from the sex column of the clicked row.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on a row triggers the va.set_control action. This uses the value of the sex column for the selected row (in other words, "Male" or "Female") to set the selector underlying vm.Filter(id="sex_filter").
  2. The change in value of vm.Filter(id="sex_filter") triggers the filter to be re-applied on its targets=["tips_graph"] so that a filtered graph is shown.

The mechanism for triggering the filter when its value is set by va.set_control is an implicit actions chain.

Tip

You can emphasize that a row is selectable by including checkboxes in your AG Grid with figure=dash_ag_grid(..., dashGridOptions={"rowSelection": {"checkboxes": True}}). The Dash AG Grid offers many options to configure row selection. These can be passed directly into dash_ag_grid as keyword arguments or set for multiple tables by creating a custom table function.

As well as being triggered on mouse click, set_control is also triggered by pressing Space while focused on a row.

Cross-filter from graph

The trigger for a cross-filter from a graph is clicking on data in the graph. The value argument of the set_control action can be used in two ways to specify what sets control:

  • Column from which to take the value. This requires you to set custom_data in the graph's figure function. For example, for a graph px.bar(..., color="country", custom_data="country") you can use va.set_control(value="country", ...).
  • As a shortcut, if the value is encoded by a positional dimension such as x or y then you can use that variable directly and do not need to set custom_data. For example, for a graph px.bar(x="country", ...) you can use va.set_control(value="x", ...). Positional dimensions include x, y, z for Cartesian plots and lat, lon, location for choropleth maps.
Behind the scenes mechanism

value is an instruction for what to lookup in Plotly's clickData, whose format and content depend on the type of chart clicked. Generally speaking, positional information is automatically included in clickData but other information such as color must be manually supplied using custom_data to make it available.

The rules for how value is interpreted by set_control are:

  1. If the graph has custom_data then interpret the value as a column name and attempt to find it in custom_data.
  2. If the graph does not have custom_data or does not include value as a column in custom_data then perform a lookup inside clickData["points"][0]. For example:
    • value="x" is equivalent to looking at clickData["points"][0]["x"].
    • value="key.subkey[1]" is equivalent to looking at clickData["points"][0]["key"]["subkey"][1].

Based on the source graph and its available clickData, you can therefore configure precisely which property to set as value. For almost all use cases, this would be a column name or a positional variable such as x. However, advanced users might like to use other data that is available in clickData such as pointNumber or to refer to an object nested deeply inside custom_data.

We show an example of each of these in turn. Here is an example where we use custom_data and value="sex" to use a value from the sex column. We need to specify custom_data because the sex column is not a positional dimension in the plot.

Cross-filter from graph to table with custom_data

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page = vm.Page(
    title="Cross-filter from graph to table",
    components=[
        vm.Graph(
            title="Click on a box to use that box's sex to filter table",
            figure=px.box(tips, x="tip", y="time", color="sex", custom_data="sex"),  # (1)!
            actions=va.set_control(control="sex_filter", value="sex"),
        ),
        vm.AgGrid(id="tips_table", figure=dash_ag_grid(tips)),  # (2)!
    ],
    controls=[vm.Filter(id="sex_filter", column="sex", targets=["tips_table"])],  # (3)!
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. We encode the sex column as color in the plot and include it in custom_data="sex".
  2. We give the vm.AgGrid an id so that it can be targeted explicitly by vm.Filter(id="sex_filter").
  3. We give the vm.Filter an id so that it can be set explicitly by va.set_control.
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: sex_filter
            type: set_control
            value: sex
        figure:
          _target_: box
          color: sex
          custom_data: sex
          data_frame: tips
          x: tip
          y: time
        title: Click on a box to use that box's sex to filter table
        type: graph
      - figure:
          _target_: dash_ag_grid
          data_frame: tips
        id: tips_table
        type: ag_grid
    controls:
      - column: sex
        id: sex_filter
        targets:
          - tips_table
        type: filter
    title: Cross-filter from graph to table

When you click on a box in the graph, the table is cross-filtered to show data for only one sex.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on the box triggers the va.set_control action. This uses the value of sex taken from the graph's custom_data (in other words, "Male" or "Female") to set the selector underlying vm.Filter(id="sex_filter").
  2. The change in value of vm.Filter(id="sex_filter") triggers the filter to be re-applied on its targets=["tips_table"] so that a filtered table is shown.

The mechanism for triggering the filter when its value is set by va.set_control is an implicit actions chain.

Cross-filter from custom chart

If you cross-filter from a custom chart and wish to use a column supplied through custom_data for the value argument of va.set_control then you must explicitly include custom_chart in the function signature:

@capture("graph")
def my_custom_chart(data_frame, custom_data, **kwargs):
    return px.scatter(data_grame, custom_data=custom_data, **kwargs)

Here is an example where we do not need to use custom_data because the value used in va.set_control is positional: it corresponds to the y axis of the graph.

Cross-filter from graph without custom_data to table

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page = vm.Page(
    title="Cross-filter from graph to table",
    components=[
        vm.Graph(
            title="Click on a box to use that box's sex to filter table",
            figure=px.box(tips, x="tip", y="sex"),
            actions=va.set_control(control="sex_filter", value="y"),
        ),
        vm.AgGrid(id="tips_table", figure=dash_ag_grid(tips)),  # (1)!
    ],
    controls=[vm.Filter(id="sex_filter", column="sex", targets=["tips_table"])],  # (2)!
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. We give the vm.AgGrid an id so that it can be targeted explicitly by vm.Filter(id="sex_filter").
  2. We give the vm.Filter an id so that it can be set explicitly by va.set_control.
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: sex_filter
            type: set_control
            value: y
        figure:
          _target_: box
          data_frame: tips
          x: tip
          y: sex
        title: Click on a box to use that box's sex to filter table
        type: graph
      - figure:
          _target_: dash_ag_grid
          data_frame: tips
        id: tips_table
        type: ag_grid
    controls:
      - column: sex
        id: sex_filter
        targets:
          - tips_table
        type: filter
    title: Cross-filter from graph to table

When you click on a box in the graph, the table is cross-filtered to show data for only one sex, which is the y variable for the plot.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on the box triggers the va.set_control action. This uses the value of y (in other words, "Male" or "Female") to set the selector underlying vm.Filter(id="sex_filter").
  2. The change in value of vm.Filter(id="sex_filter") triggers the filter to be re-applied on its targets=["tips_table"] so that a filtered table is shown.

The mechanism for triggering the filter when its value is set by va.set_control is an implicit actions chain.

Cross-filter between containers

A cross-filter often works best when used inside a container. This typically makes it clearer which components the filter applies to, especially when the container is styled.

For example, let us rearrange the above example of a cross-filter from a table into containers. Now the control appears directly above the table that it targets rather than on the left hand side of the page. The rearrangement here is purely visual to give a better user experience; va.set_control itself is configured exactly the same way and behaves identically while the dashboard is running.

Cross-filter between containers

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page = vm.Page(
    title="Cross-filter between containers",
    components=[
        vm.Container(
            components=[
                vm.AgGrid(
                    title="Click on a row to use that row's sex to filter graph",
                    figure=dash_ag_grid(tips),
                    actions=va.set_control(control="sex_filter", value="sex"),
                )
            ],
            variant="filled",  # (1)!
        ),
        vm.Container(
            components=[vm.Graph(figure=px.histogram(tips, x="tip"))],  # (2)!
            controls=[vm.Filter(id="sex_filter", column="sex")],  # (3)!
            variant="filled",
        ),
    ],
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. We use styled containers to make it clear which components and controls are in each container.
  2. The vm.Graph no longer needs an id assigned to it, since the vm.Filter does not need to explicitly target it any more.
  3. The vm.Filter no longer needs to specify targets. By default, the vm.Filter targets all components in its container whose data source includes column="sex".
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - components:
          - actions:
              - control: sex_filter
                type: set_control
                value: sex
            figure:
              _target_: dash_ag_grid
              data_frame: tips
            title: Click on a row to use that row's sex to filter graph
            type: ag_grid
        type: container
        variant: filled
      - components:
          - figure:
              _target_: histogram
              data_frame: tips
              x: tip
            type: graph
        controls:
          - column: sex
            id: sex_filter
            type: filter
        type: container
        variant: filled
    title: Cross-filter between containers

Cross-filter between pages

You can perform a cross-filter where the target components are on a different page from the source. The use of va.set_control is identical, but the intermediate filter must have show_in_url=True.

For example, let us rearrange the above example of a cross-filter from a table so that the source table is on a different page from the target graph (and hence filter). When you click or press Space on a row in the table, you are taken to the target page with the graph cross-filtered to show data only for one sex.

Cross filter between pages

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page_1 = vm.Page(
    title="Cross-filter source table",
    components=[
        vm.AgGrid(
            title="Click on a row to use that row's sex to filter graph",
            figure=dash_ag_grid(tips),
            actions=va.set_control(control="sex_filter", value="sex"),
        )
    ],
)

page_2 = vm.Page(
    title="Cross-filter target graph",
    components=[vm.Graph(figure=px.histogram(tips, x="tip"))],   # (1)!
    controls=[vm.Filter(id="sex_filter", column="sex", show_in_url=True)],  # (2)!
)

dashboard = vm.Dashboard(pages=[page_1, page_2])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. The vm.Graph no longer needs an id assigned to it, since the vm.Filter does not need to explicitly target it any more.
  2. The vm.Filter no longer needs to specify targets. By default, the vm.Filter targets all components on its page whose data source includes column="sex". We must set show_in_url=True for this filter to be set by va.set_control.
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: sex_filter
            type: set_control
            value: sex
        figure:
          _target_: dash_ag_grid
          data_frame: tips
        title: Click on a row to use that row's sex to filter graph
        type: ag_grid
    title: Cross-filter source table
  - components:
      - figure:
          _target_: histogram
          data_frame: tips
          x: tip
        type: graph
    controls:
      - column: sex
        id: sex_filter
        show_in_url: true
        type: filter
    title: Cross-filter target graph

Cross-filter from pivoted or multi-dimensional data

A single source component can trigger multiple cross-filters. For example, pivoted data can be visualized using a table or a 2-dimensional heatmap.

To perform multiple cross-filters, each dimension that is filtered must have its own vm.Filter that is set by va.set_control in the actions of the source component in an actions chain. Here is a 2-dimensional example that cross-filters from a graph using the positional variables x and y.

Cross-filter over 2 dimensions

import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid

tips = px.data.tips()

page = vm.Page(
    title="Cross-filter over 2 dimensions",
    components=[
        vm.Graph(
            title="Click on a cell to use that cell's sex and day to filter table",
            figure=px.density_heatmap(  # (1)!
                tips,
                x="day",
                y="sex",
                category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]},
            ),
            actions=[
                va.set_control(control="day_filter", value="x"),  # (2)!
                va.set_control(control="sex_filter", value="y"),
            ],
        ),
        vm.AgGrid(id="tips_table", figure=dash_ag_grid(tips)),
    ],
    controls=[
        vm.Filter(id="day_filter", column="day", targets=["tips_table"]),  # (3)!
        vm.Filter(id="sex_filter", column="sex", targets=["tips_table"]),
    ],
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. We make a 2-dimensional histogram to show the number of rows in the tips data for each day and sex.
  2. Each dimension has its own va.set_control to set the relevant vm.Filter.
  3. Each has its own vm.Filter to filter by the relevant column.
# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: day_filter
            type: set_control
            value: x
          - control: sex_filter
            type: set_control
            value: y
        figure:
          _target_: density_heatmap
          category_orders:
            day:
              - Thur
              - Fri
              - Sat
              - Sun
          data_frame: tips
          x: day
          y: sex
        title: Click on a cell to use that cell's sex and day to filter table
        type: graph
      - figure:
          _target_: dash_ag_grid
          data_frame: tips
        id: tips_table
        type: ag_grid
    controls:
      - column: day
        id: day_filter
        targets:
          - tips_table
        type: filter
      - column: sex
        id: sex_filter
        targets:
          - tips_table
        type: filter
    title: Cross-filter over 2 dimensions

When you click on a colored cell in the heatmap, the table is cross-filtered to show data for only one sex and one day. The "count" shown for each heatmap cell corresponds to the number of rows shown in the filtered table when that cell is clicked.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on a cell triggers the first va.set_control action. This uses the value of day (in other words, "Thur", "Fri", "Sat" or "Sun") to set the selector underlying vm.Filter(id="day_filter").
  2. When the day_filter has been set, the second va.set_control action runs. This uses the value of sex (in other words, "Male" or "Female") to set the selector underlying vm.Filter(id="sex_filter").
  3. The change in value of vm.Filter(id="day_filter") triggers the filter on its targets=["tips_table"] so that a filtered table is shown.
  4. The change in value of vm.Filter(id="sex_filter") triggers the filter on its targets=["tips_table"] so that a filtered table is shown.

The mechanism for triggering the filter when its value is set by va.set_control is an implicit actions chain, while the sequence of applying the two va.set_control is an explicit actions chain. In general, steps 2 and 3 above will execute in parallel.

When performing multiple filters with dynamic data, you should consider configuring a cache so that steps 3 and 4 above do not repeatedly perform a slow data load.

Multiple cross-filters are similarly possible from a table:

vm.AgGrid(
    ...,
    actions=[
        va.set_control(control="day_filter", value="day"),
        va.set_control(control="sex_filter", value="sex"),
    ],
)

However, it is not yet possible to cross-filter from a pivot table according to the row and column of the clicked cell.

Cross-parameter

A cross-parameter is when the user clicks on one source graph or table to update any argument other than data_frame of one or more target components. In Vizro, a cross-parameter operates through an intermediate parameter. To configure a cross-parameter:

  1. Create a parameter that targets the graphs, tables or figures you would like to update. The parameter must have a categorical selector (both multi- and single-option are allowed).

    import vizro.models as vm
    
    controls = [
        vm.Parameter(
            id="my_parameter",
            targets=["target_component.my_argument"],
            selector=vm.RadioItems(options=["A", "B", "C"]),
        )
    ]
    
  2. Call set_control in the actions argument of the source Graph or AgGrid component that triggers the cross-parameter.

    1. Set control to the ID of the parameter.
    2. Set value. The format of this depends on the source model and is given in the API reference. Think of it as an instruction for what to lookup in the source data: whatever value is fetched from this lookup is used to set control.
    import vizro.actions as va
    
    components = [vm.Graph(..., actions=va.set_control(control="my_parameter", value="country"))]
    

Cross-highlight

A cross-highlight is an example of a cross-parameter where the effect of the intermediate parameter is to highlight data. When a user clicks on one source graph or table, the corresponding data is highlighted in a target graph or table (typically a custom graph). The highlighting can occur in two ways:

  • Target highlighting: The highlighting appears in a target graph that is distinct from the source that was clicked. Below we give examples of highlighting a target graph from a source table and from a source graph.
  • Source highlighting or self-highlighting: The highlighting appears in the same source graph that was clicked. Below we give an example of self-highlighting a graph.

In Vizro, cross-highlighting operates through an intermediate parameter. Often this parameter is hidden from view with visible=False since the highlighting effect itself provides sufficient visual feedback about the selected data. Remember that the cross-highlight can be cleared with the "Reset controls" button.

In general, there are many different ways to visually highlight data in a graph. For example:

Tip

All cross-parameters, which includes cross-highlights, can operate across different containers and different pages. The use of va.set_control is identical to when source and target are in the same container and page. For further examples and styling hints, see the sections on cross-filtering between containers and between pages.

Cross-highlight from table

This example shows how to configure cross-highlighting where clicking on the row in a table highlights the corresponding data in a target scatter graph. The highlighting is visually shown by changing the color of the point for the selected country. Since cross-highlight is a sort of cross-parameter, the method follows the same pattern as configuring a cross-parameter.

  1. Create a parameter that targets the graph you would like to visually highlight.

    import vizro.models as vm
    
    controls = [
        vm.Parameter(
            id="highlight_parameter",  # (1)!
            targets=["scatter_chart.highlight_country"],  # (2)!
            selector=vm.RadioItems(options=["NONE", ...]),  # (3)!
            visible=False,  # (4)!
        )
    ]
    
    1. We give the parameter an id so that it can be set explicitly by va.set_control.
    2. The parameter targets the argument highlight_country of vm.Graph(id="scatter_chart").
    3. We add "NONE" as an option, corresponding to a parameter value highlight_country=None. This is used so the target graph is initially unhighlighted.
    4. We set visible=False to hide the parameter selector from the user interface while keeping the functionality active.
  2. Call set_control in the actions argument of the source AgGrid component that triggers the cross-highlight.

    1. Set control to the ID of the parameter.
    2. Set value to specify which column contains the value that sets the control when a row in the table is clicked.
    import vizro.actions as va
    
    components = [vm.AgGrid(..., actions=va.set_control(control="highlight_parameter", value="country"))]
    
  3. Create a custom chart that highlights the data corresponding to highlight_country.

    import vizro.plotly.express as px
    from vizro.models.types import capture
    
    
    @capture("graph")
    def scatter_with_highlight(data_frame, highlight_country):  # (1)!
        country_is_highlighted = data_frame["country"] == highlight_country  # (2)!
        return px.scatter(data_frame, x=..., y=..., color=country_is_highlighted)  # (3)!
    
    1. The highlight_country argument receives the selected country name from highlight_parameter.
    2. country_is_highlighted is a pandas Series that contains True for the highlighted country and False for all others.
    3. We color the points by country_is_highlighted. This will color differently the values True (for the highlighted country) and False (for all others).

The full code is given below. This shows a slightly more complicated highlighting style that also changes some additional properties of the highlighted point like its opacity.

Cross-highlight from table

import vizro.plotly.express as px
import vizro.models as vm
import vizro.actions as va
from vizro.models.types import capture
from vizro import Vizro
from vizro.tables import dash_ag_grid

gapminder = px.data.gapminder().query("continent == 'Europe' and year == 2007")

@capture("graph")
def scatter_with_highlight(data_frame, highlight_country=None):  # (1)!
    country_is_highlighted = data_frame["country"] == highlight_country  # (2)!
    fig = px.scatter(
        data_frame,
        x="gdpPercap",
        y="lifeExp",
        size="pop",
        size_max=60,
        opacity=0.3,
        color=country_is_highlighted,
        category_orders={"color": [False, True]},  # (3)!
    )

    if highlight_country is not None: # (4)!
        fig.update_traces(selector=1, marker={"line_width": 2, "opacity": 1})  # (5)!

    fig.update_layout(showlegend=False)
    return fig


page = vm.Page(
    title="Cross-highlight from table",
    layout=vm.Grid(grid=[[0, 1]], col_gap="80px"),  # (6)!
    components=[
        vm.AgGrid(
            header="💡 Click on a row to highlight that country in the scatter plot",
            figure=dash_ag_grid(data_frame=gapminder),
            actions=va.set_control(control="highlight_parameter", value="country"),  # (7)!
        ),
        vm.Graph(
            id="scatter_chart",   # (8)!
            figure=scatter_with_highlight(gapminder),
        ),
    ],
    controls=[
        vm.Parameter(
            id="highlight_parameter",   # (9)!
            targets=["scatter_chart.highlight_country"],   # (10)!
            selector=vm.RadioItems(options=["NONE", *gapminder["country"]]),   # (11)!
            visible=False,   # (12)!
        ),
    ],
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. The highlight_country argument receives the selected country name from highlight_parameter.
  2. country_is_highlighted is a pandas Series that contains True for the highlighted country and False for all others. We use this to change the color of the highlighted point.
  3. We make sure that the colors are always ordered the same way. This ensures that the highlighted point always has the same color regardless of which row in the table is clicked.
  4. When a country is highlighted, make further modifications to the style of the highlighted point's marker to make it stand out more.
  5. update_traces updates only the trace selected with index 1. The traces are ordered by category_orders={"color": [False, True]} and so this corresponds to True, in other words the trace that has country_is_highlighted=True and contains the highlighted point.
  6. We use a side-by-side layout with an 80px column gap to display the table and graph together.
  7. The table's va.set_control sets higlight_parameter to the country from the clicked row.
  8. We give the vm.Graph an id so that it can be targeted by highlight_parameter.
  9. We give the parameter an id so that it can be set explicitly by va.set_control.
  10. The parameter targets the argument highlight_country of vm.Graph(id="scatter_chart").
  11. We add "NONE" as an option, corresponding to a parameter value highlight_country=None. This is used so the target graph is initially unhighlighted.
  12. We set visible=False to hide the parameter selector from the user interface while keeping the functionality active.
# 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:
  - actions:
    - control: highlight_parameter
      type: set_control
      value: country
    figure:
      _target_: dash_ag_grid
      data_frame: gapminder
    header: 💡 Click on a row to highlight that country in the scatter plot
    type: ag_grid
  - figure:
      _target_: __main__.scatter_with_highlight
      data_frame: gapminder
    id: scatter_chart
    type: graph
  controls:
  - id: highlight_parameter
    selector:
      options:
      - NONE
      - Albania
      - ...
      - United Kingdom
      type: radio_items
    targets:
    - scatter_chart.highlight_country
    type: parameter
    visible: false
  layout:
    col_gap: 80px
    grid:
    - - 0
      - 1
    type: grid
  title: Cross-highlight from table

When you click on a row in the table, the corresponding point is highlighted in the scatter plot with an orange color, full opacity, and a thick border. Clicking the "Reset controls" button resets the parameter to its original value and hence clears the highlighting.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on a row triggers the va.set_control action. This uses the value of the country column for the selected row to set the selector underlying vm.Parameter(id="highlight_parameter").
  2. The change in value of vm.Parameter(id="highlight_parameter") triggers the parameter to update the highlight_country argument of the target component scatter_chart so that a highlighted graph is shown.

The mechanism for triggering the parameter when its value is set by va.set_control is an implicit actions chain.

Cross-highlight from graph

This example shows how to configure cross-highlighting where clicking on a point in a graph highlights the corresponding data in a target bump chart. The highlighting is visually shown by making the line for the selector country stronger. Since cross-highlight is a sort of cross-parameter, the method follows the same pattern as configuring a cross-parameter.

  1. Create a parameter that targets the graph you would like to visually highlight.

    import vizro.models as vm
    
    controls = [
        vm.Parameter(
            id="highlight_parameter",  # (1)!
            targets=["bump_chart.highlight_country"],  # (2)!
            selector=vm.RadioItems(options=["NONE", ...]),  # (3)!
            visible=False,  # (4)!
        )
    ]
    
    1. We give the parameter an id so that it can be set explicitly by va.set_control.
    2. The parameter targets the argument highlight_country of vm.Graph(id="bump_chart").
    3. We add "NONE" as an option, corresponding to a parameter value highlight_country=None. This is used so the target graph is initially unhighlighted.
    4. We set visible=False to hide the parameter selector from the user interface while keeping the functionality active.
  2. Call set_control in the actions argument of the source Graph component that triggers the cross-highlight.

    1. Set control to the ID of the parameter.
    2. Set value. As with a cross-filter from a graph, there are two different ways to specify this. However, often the value you require is encoded by a positional dimension such as x, y, z. If the value is not encoded as a positional dimension (for example, it corresponds to color) then you should instead use custom_data as described in the instructions on cross-filtering from a graph.
    import vizro.actions as va
    
    components = [
        vm.Graph(
            figure=px.bar(data_frame, x=..., y="country"),
            actions=va.set_control(control="highlight_parameter", value="y"),
        )
    ]
    
  3. Create a custom chart that highlights the data corresponding to highlight_country.

    import vizro.plotly.express as px
    from vizro.models.types import capture
    
    
    @capture("graph")
    def bump_chart_with_highlight(data_frame, highlight_country):  # (1)!
        fig = px.line(data_frame, x=..., y=..., color="country")  # (2)!
        fig.update_traces(selector={"name": highlight_country}, line_width=3)  # (3)!
        return fig
    
    1. The highlight_country argument receives the selected country name from highlight_parameter.
    2. We color the plot by country so that each country has its own trace in the resulting chart.
    3. We use update_traces to modify the highlighted line's style.

The full code is given below. This includes the complete code for a bump chart with more advanced styling.

Cross-highlight from graph

import vizro.plotly.express as px
import vizro.models as vm
import vizro.actions as va
from vizro.models.types import capture
from vizro import Vizro

selected_countries = [
    "Singapore",
    "Malaysia",
    "Thailand",
    "Indonesia",
    "Philippines",
    "Vietnam",
    "Cambodia",
    "Myanmar",
]

gapminder = px.data.gapminder().query("country.isin(@selected_countries)")

@capture("graph")
def bump_chart_with_highlight(data_frame, highlight_country=None):  # (1)!
    rank = data_frame.groupby("year")["lifeExp"].rank(method="dense", ascending=False)

    fig = px.line(data_frame, x="year", y=rank, color="country", markers=True)  # (2)!
    fig.update_yaxes(title="Rank (1 = Highest lifeExp)", autorange="reversed", dtick=1)  # (3)!
    fig.update_traces(opacity=0.3, line_width=2)  # (4)!

    if highlight_country is not None:  # (5)!
        fig.update_traces(selector={"name": highlight_country}, opacity=1, line_width=3)  # (6)!

    return fig


page = vm.Page(
    title="Cross-highlight from graph",
    components=[
        vm.Graph(
            figure=px.bar(
                gapminder.query("year == 2007"),
                y="country",
                x="lifeExp",
                labels={"lifeExp": "lifeExp in 2007"},
            ),
            header="💡 Click any bar to highlight that country in the bump chart",
            actions=va.set_control(control="highlight_parameter", value="y"),  # (7)!
        ),
        vm.Graph(
            id="bump_chart",  # (8)!
            figure=bump_chart_with_highlight(data_frame=gapminder),
        ),
    ],
    controls=[
        vm.Parameter(
            id="highlight_parameter",   # (9)!
            targets=["bump_chart.highlight_country"],   # (10)!
            selector=vm.RadioItems(options=["NONE", *gapminder["country"]]),   # (11)!
            visible=False,   # (12)!
        ),
    ],
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. The highlight_country argument receives the selected country name from highlight_parameter.
  2. rank is a pandas Series that gives the ranking of each country by life expectancy for every year. We color the plot by country so that each country has its own trace in the resulting chart.
  3. Format the bump chart's y-axis so that it shows the rank of 1 (highest life expectancy) at the top.
  4. Style the lines for every country.
  5. When a country is highlighted, modify its line's style to make it stand out more.
  6. update_traces updates only the trace selected, which is the highlighted_country one.
  7. The graph's va.set_control sets higlight_parameter to the country from the clicked bar.
  8. We give the vm.Graph an id so that it can be targeted by highlight_parameter.
  9. We give the parameter an id so that it can be set explicitly by va.set_control.
  10. The parameter targets the argument highlight_country of vm.Graph(id="bump_chart").
  11. We add "NONE" as an option, corresponding to a parameter value highlight_country=None. This is used so the target graph is initially unhighlighted.
  12. We set visible=False to hide the parameter selector from the user interface while keeping the functionality active.
# 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:
      - type: graph
        figure:
          _target_: bar
          data_frame: gapminder_2007
          labels:
            lifeExp: lifeExp in 2007
          x: lifeExp
          y: country
        header: 💡 Click any bar to highlight that country in the bump chart
        actions:
          - type: set_control
            control: highlight_parameter
            value: y
      - type: graph
        id: bump_chart
        figure:
          _target_: __main__.bump_chart_with_highlight
          data_frame: gapminder
    controls:
      - type: parameter
        id: highlight_parameter
        targets:
          - bump_chart.highlight_country
        selector:
          type: radio_items
          options:
            - NONE
            - Cambodia
            - Indonesia
            - Malaysia
            - Myanmar
            - Philippines
            - Singapore
            - Thailand
            - Vietnam
        visible: false
    title: Cross-highlight from graph

When you click on a bar in the bar chart, the corresponding line is highlighted in the bump chart with full opacity and a thicker line. Clicking the "Reset controls" button resets the parameter to its original value and hence clears the highlighting.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on a bar triggers the va.set_control action. This uses the value of y (in other words, the country) taken from the source graph to set the value of the vm.Parameter(id="highlight_parameter").
  2. The change in value of vm.Parameter(id="highlight_parameter") triggers the parameter to update the highlight_country argument of the target component bump_chart so that a highlighted graph is shown.

The mechanism for triggering the parameter when its value is set by va.set_control is an implicit actions chain.

Self-highlight a graph

In a self-highlight, the same component is both the source and the target of a cross-highlight. For example, when a user clicks on a graph, part of the graph can remain highlighted to show where they clicked. Since it is just a special type of cross-highlighting, the configuration works exactly the same way as a cross-highlight from a graph.

A self-highlight is often part of an actions chain that includes another action like a cross-filter. The example below shows how clicking a graph can perform a self-highlight and cross-filter a table.

Self-highlight a graph and cross-filter

import vizro.plotly.express as px
import vizro.models as vm
import vizro.actions as va
from vizro.models.types import capture
from vizro import Vizro
from vizro.tables import dash_ag_grid

selected_countries = [
    "Singapore",
    "Malaysia",
    "Thailand",
    "Indonesia",
    "Philippines",
    "Vietnam",
    "Cambodia",
    "Myanmar",
]

gapminder = px.data.gapminder().query("country.isin(@selected_countries)")


@capture("graph")
def bar_with_highlight(data_frame, highlight_country=None):  # (1)!
    country_is_highlighted = data_frame["country"] == highlight_country  # (2)!
    fig = px.bar(
        data_frame,
        x="lifeExp",
        y="country",
        labels={"lifeExp": "lifeExp in 2007"},
        color=country_is_highlighted,
        category_orders={"country": sorted(data_frame["country"]), "color": [False, True]},  # (3)!
    )
    fig.update_layout(showlegend=False)
    return fig


page = vm.Page(
    title="Self-highlight a graph and cross-filter",
    components=[
        vm.Graph(
            id="bar_chart",   # (4)!
            figure=bar_with_highlight(gapminder.query("year == 2007")),
            header="💡 Click on a bar to highlight the selected country and filter the table below",
            actions=[
                va.set_control(control="highlight_parameter", value="y"),   # (5)!
                va.set_control(control="country_filter", value="y"),   # (6)!
            ],
        ),
        vm.AgGrid(id="gapminder_table", figure=dash_ag_grid(data_frame=gapminder)),   # (7)!
    ],
    controls=[
        vm.Parameter(
            id="highlight_parameter",  # (8)!
            targets=["bar_chart.highlight_country"],  # (9)!
            selector=vm.RadioItems(options=["NONE", *gapminder["country"]]),  # (10)!
            visible=False,  # (11)!
        ),
        vm.Filter(id="country_filter", column="country", targets=["gapminder_table"], visible=False),  # (12)!
    ],
)

dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Run and edit this code in Py.Cafe

  1. The highlight_country argument receives the selected country name from highlight_parameter.
  2. country_is_highlighted is a pandas Series that contains True for the highlighted country and False for all others. We use this to change the color of the highlighted bar.
  3. Order the bars according to the country alphabetically. We also make sure that the colors are always ordered the same way to ensure that the highlighted bar always has the same color.
  4. We give the vm.Graph an id so that it can be targeted by highlight_parameter.
  5. This va.set_control sets higlight_parameter to the country from the clicked bar.
  6. This va.set_control sets country_filter to the country from the clicked bar.
  7. We give the vm.AgGrid an id so that it can be targeted by country_filter.
  8. We give the parameter an id so that it can be set explicitly by va.set_control.
  9. The parameter targets the argument highlight_country of vm.Graph(id="bar_chart").
  10. We add "NONE" as an option, corresponding to a parameter value highlight_country=None. This is used so the bar chart is initially unhighlighted.
  11. We set visible=False to hide the parameter selector from the user interface while keeping the functionality active.
  12. Filter gapminder_table to show rows for only the selected country.
# 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:
      - type: graph
        id: bar_chart
        figure:
          _target_: __main__.bar_with_highlight
          data_frame: gapminder_2007
        header: 💡 Click on a bar to highlight the selected country and filter the table below
        actions:
          - type: set_control
            control: highlight_parameter
            value: y
          - type: set_control
            control: country_filter
            value: y
      - type: ag_grid
        id: gapminder_table
        figure:
          _target_: dash_ag_grid
          data_frame: gapminder
    controls:
      - id: highlight_parameter
        selector:
          options:
            - NONE
            - Cambodia
            - Indonesia
            - Malaysia
            - Myanmar
            - Philippines
            - Singapore
            - Thailand
            - Vietnam
          type: radio_items
        targets:
          - bar_chart.highlight_country
        type: parameter
        visible: false
      - column: country
        id: country_filter
        targets:
          - gapminder_table
        type: filter
        visible: false
    title: Self-highlight a graph and cross-filter

When you click on a bar in the chart, that bar is highlighted in orange and the table below is filtered to show only data for the selected country. Clicking the "Reset controls" button resets the parameter and the filter to their original values and hence clears the highlighting and filtering.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Clicking on a bar triggers the va.set_control action. This uses the value of y (in other words, the country) taken from the source graph to set the value of the vm.Parameter(id="highlight_parameter").
  2. When the highlight_parameter has been set, the second va.set_control action runs. This also uses the value of y (in other words, the country) to set the selector underlying vm.Filter(id="country_filter").
  3. The change in value of vm.Parameter(id="highlight_parameter") triggers the parameter to update the highlight_country argument of the target component bar_chart so that a highlighted graph is shown.
  4. The change in value of vm.Filter(id="country_filter") triggers the filter on its targets=["gapminder_table"] so that a filtered table is shown.

The mechanism for triggering the parameter and filter when their values are set by va.set_control is an implicit actions chain, while the sequence of applying the two va.set_control is an explicit actions chain. In general, steps 2 and 3 above will execute in parallel.

When performing multiple filters with dynamic data, you should consider configuring a cache so that steps 3 and 4 above do not repeatedly perform a slow data load.