Search code examples
pythonpanelaltair

Altair + panel: chart dynamic dropdown filter doesn't work


I almost have it, but the filter selection only works when executing the cell in jupyter. It doesn't get updated when the dropdown selector is used. I have three parts:

  1. A scatterplot that has to have a filter/dropdown menu bound to a list of values from a df's column Island.
  2. Altair brush of type selection_interval that is connected to the scatterplot.
  3. When the scatterplot is rendered in (jupyter) and the brush selector is used, a table based on selected records form the df is rendered below the scatterplot and the records shown in the table are dynamically presented based on the brush selection.

It almost work perfectly, but the dropdown menu doesn't get updated for the scatterplot, BUT DOES get updated for the table. What am I missing here? I need the dropdown to filter out the chart too. Below is the code and the animated gif

import panel as pn
import pandas as pd
import altair as alt
pn.extension('vega', template='fast-list')

penguins_url = "https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json"
df = pd.read_json(penguins_url)

brush = alt.selection_interval(name='brush')  # selection of type "interval"
island = pn.widgets.Select(name='Island', options=df.Island.unique().tolist())

chart = alt.Chart(df.query(f'Island == "{island.value}"')).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=700,
    height=200
).add_selection(brush)

vega_pane = pn.pane.Vega(chart, debounce=5)

def filtered_table(selection, island):    
    if not selection:
        return '## No selection'
    query = ' & '.join(
        f'{crange[0]:.3f} <= `{col}` <= {crange[1]:.3f} & Island == "{island}"'
        for col, crange in selection.items()
    )
    return pn.Column(
        f'Query: {query}',
        pn.pane.DataFrame(df.query(query).query(f'Island == "{island}"'), width=600, height=300)
    )

pn.Column(
    pn.Row(island, vega_pane),
    pn.bind(filtered_table, selection = vega_pane.selection.param.brush, island=island))[![enter image description here][1]][1]

chart rendering and faulty filtering


Solution

  • There are a few examples of how to achieve this in https://github.com/holoviz/panel/issues/4487, e.g.:

    def filter_table(data, 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(data.query(query), width=600, height=300)
        )
    
    penguins_url = "https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json"
    df = pd.read_json(penguins_url)
    
    island_dropdown = pn.widgets.Select(options=df['Island'].unique().tolist(), width=300)
    
    dynamic_data = pn.bind(lambda island: df.loc[df['Island'] == island], island_dropdown)
    
    brush = alt.selection_interval(name='brush')  # selection of type "interval"
    chart = alt.Chart(dynamic_data()).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'))
    ).add_params(
        brush
    )
    
    altair_pane = pn.pane.Vega(chart)
    
    def update_chart(_):
        altair_pane.object = chart.properties(data=dynamic_data())
        
    island_dropdown.param.watch(update_chart, 'value')
    
    pn.Column(
        island_dropdown,
        pn.Row(
            altair_pane,
            pn.bind(filter_table, dynamic_data, altair_pane.selection.param.brush)
        )
    )