Search code examples
pythonsignals-slotsaltairvega-litevega

Workaround for externally emitting signals/events from interactions with VegaLite/Altair plots?


To my understanding, there is no official support for emitting signals/events from interactions in VegaLite/Altair, such as selecting points in a scatter plot, other than to charts in the same view/composition.

Is there currently any workaround for this that would capture which points are selected in a scatter plot and process them outside VegaLite ("outside" = in Python for me as I am using Altair)?

For example, could I make a selection persistent and saved to a json spec that I could parse in Python? Or could I write a custom javascript function that is triggerd by a chart selection and saved as a Python variable or printed to some stream that I could read from Python?

Example code from the docs for a chart with an interactive selection:

import altair as alt
from vega_datasets import data

brush = alt.selection_interval() 
chart = alt.Chart(data.cars.url).mark_point().encode(
    x='Horsepower:Q',
    y='Displacement:Q',
    color=alt.condition(brush, 'Origin:N', alt.value('lightgray')),
    tooltip='Horsepower:Q').add_selection(brush)
chart

Solution

  • The capability to listen to Vega events and define custom callback for e.g. selected points was recently merged in Panel and is included in the 0.13 release! ~~This is the only Python dashboarding package that supports custom callbacks on selections in Altair charts~~ edit: this is now also supported in Dash and Shiny). Here is an example from the docs:

    penguins_url = "https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json"
    brush = alt.selection_interval(name='brush')  # selection of type "interval"
    
    chart = alt.Chart(penguins_url).mark_point().encode(
        x=alt.X('Beak Length (mm):Q', scale=alt.Scale(zero=False)),
        y=alt.Y('Beak Depth (mm):Q', scale=alt.Scale(zero=False)),
        color=alt.condition(brush, 'Species:N', alt.value('lightgray'))
    ).properties(
        width=250,
        height=250
    ).add_selection(
        brush
    )
    
    vega_pane = pn.pane.Vega(chart, debounce=10)
    
    vega_pane
    
    df = pd.read_json(penguins_url)
    
    def filtered_table(selection):
        if not selection:
            return '## No selection'
        query = ' & '.join(
            f'{crange[0]:.3f} <= `{col}` <= {crange[1]:.3f}'
            for col, crange in selection.items()
        )
        return pn.Column(
            f'Query: {query}',
            pn.pane.DataFrame(df.query(query), width=600, height=300)
        )
    
    pn.Row(vega_pane, pn.bind(filtered_table, vega_pane.selection.param.brush))
    

    image