Bindings & Widgets#

With an understanding of the parameter types and conditions, you can now bind parameters to chart elements (e.g. legends) and widgets (e.g. drop-downs and sliders). This is done using the bind option inside param and selection. As specified by the Vega-lite binding docs, there are three types of bindings available:

  1. Point and interval selections can be used for data-driven interactive elements, such as highlighting and filtering based on values in the data.

  2. Sliders and checkboxes can be used for logic-driven interactive elements, such as highlighting and filtering based on the absolute values in these widgets.

  3. Interval selections can be bound to a scale, such as zooming in on a map.

The following table summarizes the input elements that are supported in Vega-Lite:

Input Element

Description

Example

binding_checkbox

Renders as checkboxes allowing for multiple selections of items.

Multiple Interactions

binding_radio

Radio buttons that force only a single selection

Multiple Interactions

binding_select

Drop down box for selecting a single item from a list

Multiple Interactions

binding_range

Shown as a slider to allow for selection along a scale.

US Population by Age and Sex

binding

General method that supports many HTML input elements

Widget Binding#

Widgets are HTML input elements, such as drop-downs, sliders, radio buttons, and search boxes. There are a three strategies for how variable and selection parameters can be used together with widgets: data-driven lookups, data-driven comparisons, and logic-driven comparisons.

Data-Driven Lookups#

Data-driven lookups use the active value(s) of the widget together with a selection parameter to look up points with matching values in the chart’s dataset. For example, we can establish a binding between an input widget and a point selection to filter the data as in the example below where a drop-down is used to highlight cars of a specific Origin:

import altair as alt
from vega_datasets import data

cars = data.cars.url

input_dropdown = alt.binding_select(options=['Europe', 'Japan', 'USA'], name='Region ')
selection = alt.selection_point(fields=['Origin'], bind=input_dropdown)
color = (
    alt.when(selection)
    .then(alt.Color("Origin:N").legend(None))
    .otherwise(alt.value("lightgray"))
)

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=color,
).add_params(
    selection
)

Note that although it looks like a value is selected in the dropdown from the start, we need to set value= to actually start out with an initial selection in the chart. We did this previously with variable parameters and selection parameters follow the same pattern as you will see further down in the Encoding Channel Binding section.

As you can see above, we are still using conditions to make the chart respond to the selection, just as we did without widgets. Bindings and input elements can also be used to filter data allowing the user to see just the selected points as in the example below. In this example, we also add an empty selection to illustrate how to revert to showing all points after a selection has been made in a radio button or drop-down (which cannot be deselected).

# Make radio button less cramped by adding a space after each label
# The spacing will only show up in your IDE, not on this doc page
options = ['Europe', 'Japan', 'USA']
labels = [option + ' ' for option in options]

input_dropdown = alt.binding_radio(
    # Add the empty selection which shows all when clicked
    options=options + [None],
    labels=labels + ['All'],
    name='Region: '
)
selection = alt.selection_point(
    fields=['Origin'],
    bind=input_dropdown,
)

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    # We need to set a constant domain to preserve the colors
    # when only one region is shown at a time
    color=alt.Color('Origin:N').scale(domain=options),
).add_params(
    selection
).transform_filter(
    selection
)

In addition to the widgets listed in the table above, Altair has access to any html widget via the more general binding function. In the example below, we use a search input to filter points that match the search string exactly. You can hover over the points to see the car names and try typing one into the search box, e.g. vw pickup to see the point highlighted (you need to type out the full name).

search_input = alt.selection_point(
    fields=['Name'],
    empty=False,  # Start with no points selected
    bind=alt.binding(
        input='search',
        placeholder="Car model",
        name='Search ',
    )
)
alt.Chart(data.cars.url).mark_point(size=60).encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    tooltip='Name:N',
    opacity=alt.when(search_input).then(alt.value(1)).otherwise(alt.value(0.05)),
).add_params(
    search_input
)

It is not always useful to require an exact match to the search syntax, and when we will be learning about Expressions, we will see how we can match partial strings via a regex instead.

Data-Driven Comparisons#

So far we have seen the use of selections to lookup points with precisely matching values in our data. This is often useful, but sometimes we might want to make a more complex comparison than an exact match. For example, we might want to create a condition we select the points in the data that are above or below a threshold value, which is specified via a slider. For this workflow it is recommended to use variable parameters via param and as you can see below, we use the special syntax datum.xval to reference the column to compare against. Prefixing the column name with datum tells Altair that we want to compare to a column in the dataframe, rather than to a Python variable called xval, which would have been the case if we just wrote xval < selector.

import numpy as np
import pandas as pd


rand = np.random.RandomState(42)
df = pd.DataFrame({
    'xval': range(100),
    'yval': rand.randn(100).cumsum()
})

slider = alt.binding_range(min=0, max=100, step=1, name='Cutoff ')
selector = alt.param(name='SelectorName', value=50, bind=slider)
predicate = alt.datum.xval < selector

alt.Chart(df).mark_point().encode(
   x='xval',
   y='yval',
   color=alt.when(predicate).then(alt.value("red")).otherwise(alt.value("blue")),
).add_params(
   selector
)

In this particular case we could actually have used a selection parameter since selection values can be accessed directly and used in expressions that affect the chart. For example, here we create a slider to choose a cutoff value, and color points based on whether they are smaller or larger than the value:

slider = alt.binding_range(min=0, max=100, step=1, name='Cutoff ')
selector = alt.selection_point(
    name="SelectorName",
    fields=['cutoff'],
    bind=slider,
    value=[{'cutoff': 50}]
)
predicate = alt.datum.xval < selector.cutoff

alt.Chart(df).mark_point().encode(
    x='xval',
    y='yval',
    color=alt.when(predicate).then(alt.value("red")).otherwise(alt.value("blue")),
).add_params(
    selector
)

While it can be useful to know how to access selection values in expression strings, using the parameters syntax introduced in Altair 5 often provides a more convenient syntax for simple interactions like this one since they can also be accessed in expression strings as we saw above. Similarly, it is often possible to use equality statements such as alt.datum.xval == selector to lookup exact values but it is often more convenient to switch to a selection parameter and specify a field/encoding.

Logic-Driven Comparisons#

A logic comparison is a type of comparison that is based on logical rules and conditions, rather than on the actual data values themselves. For example, for a checkbox widget we want to check if the state of the checkbox is True or False and execute some action depending on whether it is checked or not. When we are using a checkbox as a toggle like this, we need to use param instead of selection_point, since we don’t want to check if there are True/False values in our data, just if the value of the check box is True (checked) or False (unchecked):

bind_checkbox = alt.binding_checkbox(name='Scale point size by "Acceleration": ')
param_checkbox = alt.param(bind=bind_checkbox)

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    size=alt.when(param_checkbox).then("Acceleration:Q").otherwise(alt.value(25)),
).add_params(
    param_checkbox
)

Another example of creating a widget binding that is independent of the data, involves an interesting use case for the more general binding function. In the next example, this function introduces a color picker where the user can choose the colors of the chart interactively:

color_usa = alt.param(value="#317bb4", bind=alt.binding(input='color', name='USA '))
color_europe = alt.param(value="#ffb54d", bind=alt.binding(input='color', name='Europe '))
color_japan = alt.param(value="#adadad", bind=alt.binding(input='color', name='Japan '))

alt.Chart(data.cars.url).mark_circle().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.Color(
        'Origin:N',
        scale=alt.Scale(
            domain=['USA', 'Europe', 'Japan'],
            range=[color_usa, color_europe, color_japan]
        )
    )
).add_params(
    color_usa, color_europe, color_japan
)

Legend Binding#

An interactive legend can often be helpful to assist in focusing in on groups of data. Instead of manually having to build a separate chart to use as a legend, Altair provides the bind='legend' option to facilitate the creation of clickable legends:

selection = alt.selection_point(fields=['Origin'], bind='legend')

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N',
    opacity=alt.when(selection).then(alt.value(0.8)).otherwise(alt.value(0.2)),
).add_params(
    selection
)

Scale Binding#

With interval selections, the bind property can be set to the value of "scales". In these cases, the binding will automatically respond to the panning and zooming along the chart:

selection = alt.selection_interval(bind='scales')

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N',
).add_params(
    selection
)

Because this is such a common pattern, Altair provides the interactive() method which creates a scale-bound selection more concisely:

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N',
).interactive()

Encoding Channel Binding#

To update which columns are displayed in a chart based on the selection in a widget, we would need to bind the widget to an encoding channel. In contrast to legend and scale bindings, it is not possible to setup a binding to an encoding channel in the selection initialization (e.g. by typing bind='x'). Instead, parameters can be used to pass the value of a selection to an encoding channel. This gives more flexibility, but requires the use of a separate calculation transform (as in the example below) until vega/vega-lite#7365 is resolved.

In this example, we access the parameter value by referencing the parameter by name. By indexing the data with the parameter value (via datum[]) we can extract the data column that matches the selected value of the parameter, and populate the x-channel with the values from this data column.

dropdown = alt.binding_select(
    options=['Horsepower', 'Displacement', 'Weight_in_lbs', 'Acceleration'],
    name='X-axis column '
)
xcol_param = alt.param(
    value='Horsepower',
    bind=dropdown
)

alt.Chart(data.cars.url).mark_circle().encode(
    x=alt.X('x:Q').title(''),
    y='Miles_per_Gallon:Q',
    color='Origin:N'
).transform_calculate(
    x=f'datum[{xcol_param.name}]'
).add_params(
    xcol_param
)

Using parameters inside calculate transforms allows us to define dynamic computations (e.g. subtracting different pairs of columns), as you can see in the Interactive Selection of Columns gallery example. In that example, the chart title is also dynamically updated using a parameter inside an expression which is described in more detail in Inline Expressions in Titles. Note that it is currently not possible to change the axis titles dynamically based on the selected parameter value, but a text mark could be used instead (as in this SO answer), until vega/vega-lite#7264 is resolved.