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 can have any type of selector. For non-categorical selectors, see cross-filter with non-categorical selectors.

    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, selecting a row's checkbox or clicking a cell in the table. The value argument of the set_control action tells the action what to send to the control:

  • "cell" uses the clicked cell’s value.
  • "column" uses the clicked cell’s column id, which corresponds to the DataFrame column name.
  • "row" uses the clicked cell’s row id. The row id is AG Grid's rowId, which defaults to the row's index unless you configure getRowId in dashGridOptions.
  • Any other string is treated as a column name and values are taken from selected rows.

Cross-filter from table to graph - sex column value from selected rows

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

In the example above, when you select one or more rows in the table, the graph is cross-filtered to the corresponding sex values from those rows. Which cell you click does not change which field is used: the action always reads the sex column for the current row selection, not the clicked column.

Behind the scenes mechanism

In full, what happens is as follows:

  1. Changing row selection (for example, by clicking a row) triggers the va.set_control action. This uses the sex column value(s) for the selected row(s) (in other words, "Male" and/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.

When all rows are deselected, the control resets to its original value. This effectively clears any cross-filter or cross-parameter that was applied. You can also reset all controls on a page by clicking the "Reset controls" button.

If you set the set_control.value argument to value="cell", the value of the clicked cell propagates to the control.

Cross-filter from table to graph - propagating cell value

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 cell to use that cell's value to filter graph",
            figure=dash_ag_grid(tips),
            actions=va.set_control(control="sex_filter", value="cell"),
        ),
        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: cell
        figure:
          _target_: dash_ag_grid
          data_frame: tips
        title: Click on a row to use that row's value 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

Multi-select depends on how you set value in set_control:

  • If value is a column name (values are taken from selected rows), multi-row selection is turned on by default, including checkboxes.
  • If value is "cell", "column", or "row", multi-select is not available as interaction is driven by single cell clicks.

Tip

You can still customize selection with dashGridOptions on dash_ag_grid(...), for example figure=dash_ag_grid(..., dashGridOptions={"rowSelection": {"mode": "singleRow"}}) when you use a column name and want only one row selected. 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.

When multi-row (or single-row) selection is enabled, click checkboxes or click any cell to select or deselect its row. Alternatively, set_control is also triggered by pressing Space while focused on a row.

Ranges of rows can be selected by holding down Shift while clicking on rows. This behavior also applies when checkbox selection is disabled, and in group selection. Ranges of rows can be selected by holding down Shift while clicking on rows.

Use Cmd click (or Ctrl click on Windows/Linux) to select or deselect individual rows one by one. This behavior also applies when checkbox selection is disabled, and in group selection. See the Dash AG Grid multi-row selection documentation for more examples.

If you use multi-row selection but your filter or parameter uses a single-value selector (for example vm.RadioItems()), the control only updates when exactly one row is selected. Selecting two or more rows leaves the control unchanged.

Cross-filter from graph

The trigger for a cross-filter from a graph is clicking on data in the graph. A single click sends one value to the control. You can also use box/lasso select to select multiple data points at once; see Cross-filter from graph - multi-select for details and examples.

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 (or using box/lasso select on multiple data points) 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"). When multiple points are selected, unique values across all points are sent.
  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 (or using box/lasso select on multiple data points) 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"). When multiple points are selected, unique values across all points are sent.
  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 graph - multi-select

In addition to clicking on a single data point, graphs also support box select and lasso select to select multiple data points at once. When multiple points are selected, the unique values across all selected points are sent to the control.

Vizro automatically turns on click selection (clickmode is set to "event+select") and keeps the box and lasso tools in the graph's modebar when a graph has actions. This means that when a graph's point is clicked, it is highlighted. Also, holding Shift while clicking lets you select or deselect individual points on the graph.

Cross-filter from graph with multi-select

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 with selection",
    components=[
        vm.Graph(
            title="Use box or lasso select to filter by day",
            figure=px.scatter(tips, x="total_bill", y="tip", color="day", custom_data="day"),
            actions=va.set_control(control="day_filter", value="day"),
        ),
        vm.Graph(
            id="total_tips",
            title="Total tips by day",
            figure=px.bar(tips, x="day", y="tip", color="day"),
        ),
    ],
    controls=[vm.Filter(id="day_filter", column="day", targets=["total_tips"])],
)

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

Run and edit this code in Py.Cafe

# 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: day
        figure:
          _target_: scatter
          color: day
          custom_data: day
          data_frame: tips
          x: total_bill
          y: tip
        title: Use box or lasso select to filter by day
        type: graph
      - figure:
          _target_: bar
          color: day
          data_frame: tips
          x: day
          y: tip
        id: total_tips
        title: Total tips by day
        type: graph
    controls:
      - column: day
        id: day_filter
        targets:
          - total_tips
        type: filter
    title: Cross-filter from graph with selection

When select multiple points in the scatter plot, the bar chart is cross-filtered to show data for all the selected days. If only one point is clicked, the filter is set to that single value. If you deselect all points the control resets to its original value. You can deselect all points by double clicking on an empty area of the plot or by clicking on the last single selected point.

Behind the scenes mechanism

In full, what happens is as follows:

  1. A client-side callback combines the graph's clickData and selectedData into a single trigger. If box or lasso selection is used, the trigger contains all selected points; otherwise it contains the single clicked point.
  2. Graph._get_value_from_trigger iterates over all points in the trigger, extracts the unique values for the specified value, and returns a sorted list.
  3. The set_control action receives this list and sets the control accordingly: for a multi-value selector the full list is used, for a range selector the min and max are used, and for a single-value selector the control only updates when exactly one value is present.
  4. If no points are selected (deselection), None is returned, which resets the control to its original value.

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

Tip

If you use box/lasso selection but your filter or parameter uses a single-value selector (for example vm.Dropdown(multi=False)), the control only updates when exactly one point is selected. Selecting two or more points with different values leaves the control unchanged.

You can override the automatic clickmode by setting it explicitly in your figure function, for example fig.update_layout(clickmode="event") to disable highlighting points when clicked and Shift + click possibility.

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 - from a 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 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"). When multiple points are selected, unique values across all points are sent.
  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"),
    ],
)

Cross-filter over 2 dimensions - from a 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()
pivot_tips = (
    tips.pivot_table(index="sex", columns="day", aggfunc="size", fill_value=0)
    .reindex(columns=["Thur", "Fri", "Sat", "Sun"])
    .reset_index()
)


page = vm.Page(
    title="dash_ag_grid using cellClicked",
    components=[
        vm.AgGrid(
            title="set_control.value=column",
            figure=dash_ag_grid(pivot_tips),   # (1)!
            actions=[
                va.set_control(control="day_filter", value="column"),  # (2)!
                va.set_control(control="sex_filter", value="sex"),
            ],
        ),
        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 AgGrid 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: column
          - control: sex_filter
            type: set_control
            value: sex
        figure:
          _target_: dash_ag_grid
          data_frame: pivot_tips
        title: set_control.value=column
        type: ag_grid
      - 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: dash_ag_grid using cellClicked

Cross-filter with non-categorical selectors

The examples above use categorical selectors such as Dropdown and Checklist, but you can target non-categorical selectors as well with set_control action. The example below uses (vm.DatePicker(range=True)):

Cross-filter from table with non-categorical selector

import pandas as pd

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

stocks = px.data.stocks()
stocks["date"] = pd.to_datetime(stocks["date"])

page = vm.Page(
    title="Cross-filter with DatePicker",
    components=[
        vm.Graph(
            id="stocks_graph",
            title="GOOG vs AAPL Price Relationship",
            figure=px.scatter(stocks, x="GOOG", y="AAPL", custom_data="date"),
            actions=va.set_control(control="date_filter", value="date"),
        ),
        vm.Graph(
            id="stocks_graph_2",
            title="Stock Prices (Selected Points)",
            figure=px.line(stocks, x="date", y=["GOOG", "AAPL", "AMZN", "MSFT"])
        )
    ],
    controls=[
        vm.Filter(
            id="date_filter",
            column="date",
            targets=["stocks_graph_2"],
            selector=vm.DatePicker(range=True),
        ),
    ],
)

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

Run and edit this code in Py.Cafe

# Still requires a .py to add data to the data manager and parse YAML configuration
# See yaml_version example
pages:
  - components:
      - actions:
          - control: date_filter
            type: set_control
            value: date
        figure:
          _target_: scatter
          data_frame: stocks
          x: GOOG
          y: AAPL
          custom_data: date
        id: stocks_graph
        title: GOOG vs AAPL Price Relationship
        type: graph
      - figure:
          _target_: line
          data_frame: stocks
          x: date
          y:
            - GOOG
            - AAPL
            - AMZN
            - MSFT
        id: stocks_graph_2
        title: Stock Prices (Selected Points)
        type: graph
    controls:
      - column: date
        id: date_filter
        selector:
          range: true
          type: date_picker
        targets:
          - stocks_graph_2
        type: filter
    title: Cross-filter with DatePicker

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 can have any type of selector.

    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).

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.