JupyterChart#

The JupyterChart class, introduced in Vega-Altair 5.1, makes it possible to update charts after they have been displayed and access the state of Interactive Charts from Python.

Supported Environments#

JupyterChart is a Jupyter Widget built on the AnyWidget library. As such, it’s compatible with development environments and dashboard toolkits that support third party Jupyter Widgets. Tested environments include:

  • Classic Jupyter Notebook

  • JupyterLab

  • Visual Studio Code

  • Google Colab

  • Voila

Note

If you try JupyterChart in another environment that supports Jupyter Widgets, let us know how it goes so that we can keep this list up to date.

Basic Usage#

To create a JupyterChart, pass a regular Chart instance to the alt.JupyterChart constructor. The chart will be displayed automatically if the last expression in a notebook cell evaluates to a JupyterChart instance. For example:

import altair as alt
import pandas as pd

source = pd.DataFrame({
    'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
    'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]
})

chart = alt.Chart(source).mark_bar().encode(
    x='a',
    y='b'
)

jchart = alt.JupyterChart(chart)
jchart
Bar chart with letters A through I on the x-axis

Updating Charts#

The JupyterChart’s chart property can be assigned to a new chart instance, and the new chart will immediately be displayed in place of the old one.

jchart.chart = chart.mark_bar(color="crimson", cornerRadius=10)

Params: Variables and Selections#

As described in Interactive Charts, Vega-Altair’s rich grammar of interactivity is built on the concept of parameters. In particular, variable parameters (which store a simple value) and selection parameters (which map user interactions to data queries).

The JupyterChart class makes both variable and selection parameters available for use in Python.

Variable Params#

JupyterChart makes it possible to access, observe, set, and link variable parameters.

Accessing Variable Params#

A chart’s variable parameters are stored in the params property of the JupyterChart instance. The values of individual named variable parameters may be accessed using regular attribute access. Here is an example that uses Bindings & Widgets to bind a variable parameter named cutoff to a slider. The current value of the cutoff variable is available as jchart.params.cutoff.

import altair as alt
import pandas as pd
import numpy as np

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)
cutoff = alt.param(name="cutoff", bind=slider, value=50)
predicate = alt.datum.xval < cutoff

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

Observing Variable Params#

The observe method on the params property may be used to register a callback that will be invoked when a parameter changes. In this example, a simple callback function is registered to print the value of the cutoff parameter.

def on_cutoff_change(change):
    print(change.new)

jchart.params.observe(on_cutoff_change, ["cutoff"])

Setting Variable Params#

The value of variable parameters may be updated from Python by assigning to the corresponding params attribute. Here’s an example of updating the cutoff variable parameter by assigning to jchart.params.cutoff.

Linking Variable Params#

Because params is a traitlet object, it’s possible to use the ipywidgets link function to bind params to other ipywidgets. Here is an example of linking the cutoff variable parameter to the value of an ipywidgets IntSlider.

from ipywidgets import IntSlider, link
slider = IntSlider(23, min=0, max=100)
link((slider, "value"), (jchart.params, "cutoff"))
slider

If an ipywidget is linked to a Vega-Altair variable param, it’s not necessary to also bind the param to a Vega-Altair widget. Here, the example above is updated to control the cutoff variable’s value only from the IntSlider ipywidget.

import pandas as pd
import numpy as np

rand = np.random.RandomState(42)

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

cutoff = alt.param(name="cutoff", value=50)
predicate = alt.datum.xval < cutoff

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

Selection Params#

JupyterChart makes it possible to access and observe selection parameters. For the purpose of accessing selections from Python, selection parameters are divided into three types: Point selections, index selections, and interval selection. These selection types are represented by Python classes named PointSelection, IndexSelection, and IntervalSelection respectively.

Instances of these selection classes are available as properties of the JupyterChart’s selections property.

Point Selections#

The PointSelection class is used to store the current state of a Vega-Altair point selection (as created by alt.selection_point()) when either a fields or encodings specification is provided. One common example is a point selection with encodings=["color"] that is bound to the legend.

import altair as alt
from vega_datasets import data

source = data.cars()
brush = alt.selection_point(name="point", encodings=["color"], bind="legend")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

The PointSelection instance may be accessed as jchart.selections.point (Where “point” is the value of the name argument to alt.selection_point).

The jchart.selections.point.value property contains a list of dictionaries where each element represents a single point in the selection. This list of dictionaries may be converted into a pandas query string as follows

filter = " or ".join([
    " and ".join([
        f"`{col}` == {repr(val)}" for col, val in sel.items()
    ])
    for sel in jchart.selections.point.value
])
source.query(filter)

For example, when the Japan and Europe legend entries are selected, the filter string above will evaluate to "`Origin` == 'Japan' or `Origin` == 'Europe'", and the source.query(filter) expression will evaluate to a pandas DataFrame containing the rows of source that are in the selection.

Index Selections#

The IndexSelection class is used to store the current state of a Vega-Altair point selection (as created by alt.selection_point()) when neither a fields nor encodings specification is provided. In this case, the value property of the selection is a list of the indices of the selected rows. These indices can be used with the pandas DataFrame’s iloc attribute to extract the selected rows in the input DataFrame.

import altair as alt
from vega_datasets import data

source = data.cars()
brush = alt.selection_point(name="point")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

Warning

The indices returned will only correspond to the input DataFrame for charts that do not include aggregations. If a chart includes aggregations, then the alt.selection_point specification should include either a fields or encodings argument, which will result in the JupyterChart containing a PointSelection rather than an IndexSelection.

Interval Selections#

The IntervalSelection class is used to store the current state of a Vega-Altair interval selection (as created by alt.selection_interval()). In this case, the value property of the selection is a dictionary from column names to selection intervals

import altair as alt
from vega_datasets import data

source = data.cars()
brush = alt.selection_interval(name="interval")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Cylinders:O").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

The selection dictionary may be converted into a pandas query string as follows

filter = " and ".join([
    f"{v[0]} <= `{k}` <= {v[1]}"
    for k, v in jchart.selections.interval.value.items()
])
source.query(filter)

For example, when the x-selection is from 120 to 160 and the y-selection is from 25 to 35, jchart.selections.interval.value will be {'Horsepower': [120, 160], 'Miles_per_Gallon': [25, 30]}, the filter string will be "120 <= `Horsepower` <= 160 and 25 <= `Miles_per_Gallon` <= 35", and the source.query(filter) expression will evaluate to a pandas DataFrame that contains the rows of source that are in the selection.

Observing Selections#

As with variable parameters, it’s possible to register a callback function to be invoked when a selection changes by using the observe method on the selections property. Here is an example that listens for changes to an interval selection, then uses the selection value to filter the input DataFrame and display it’s HTML representation. An ipywidgets VBox is used to combine the chart and HTML table in a column layout.

import ipywidgets
from IPython.display import display
from ipywidgets import HTML, VBox

import altair as alt
from vega_datasets import data

source = data.cars()
brush = alt.selection_interval(name="brush")

chart_widget = alt.JupyterChart(alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Cylinders:O").otherwise(alt.value("grey")),
).add_params(brush))

table_widget = HTML(value=source.iloc[:0].to_html())

def on_select(change):
    sel = change.new.value
    if sel is None or 'Horsepower' not in sel:
        filtered = source.iloc[:0]
    else:
        filter_query = (
            f"{sel['Horsepower'][0]} <= `Horsepower` <= {sel['Horsepower'][1]} and "
            f"{sel['Miles_per_Gallon'][0]} <= `Miles_per_Gallon` <= {sel['Miles_per_Gallon'][1]}"
        )
        filtered = source.query(filter_query)

    table_widget.value = filtered.to_html()

chart_widget.selections.observe(on_select, ["brush"])

VBox([chart_widget, table_widget])

Offline Usage#

By default, the JupyterChart widget loads its JavaScript dependencies dynamically from a CDN location, which requires an active internet connection. Starting in Altair 5.3, JupyterChart supports loading its JavaScript dependencies from the vl-convert-python package, which enables offline usage.

Offline mode is enabled using the JupyterChart.enable_offline class method.

import altair as alt
alt.JupyterChart.enable_offline()

This only needs to be called once, after which all displayed JupyterCharts will operate in offline mode.

Offline mode can be disabled by passing offline=False to this same method.

import altair as alt
alt.JupyterChart.enable_offline(offline=False)

Limitations#

Setting Selections#

It’s not currently possible to set selection states from Python.