Search code examples
pythonplotinteractivevega-litealtair

Is it possible to create an Altair binding to a datalist element instead of select?


I am attempting to set up an interactive filter of gene expression on a time-series plot. The documented method for creating this kind of filter is with select_single bound to an input form. For smaller numbers of options, a binding_select would work. E.g.

import altair as alt
group_dropdown = alt.binding_select(options=gene_names)
group_select = alt.selection_single(fields=['gene'], bind=group_dropdown, name='Feature', init={'gene': gene_names[0]})
filter_group = chart.add_selection(group_select).transform_filter(group_select)

However, I have ~50K genes that could be selected, so a dropdown (binding_select) isn't really an option. A <datalist> element would be perfect. The vega-lite docs on Input Binding imply that I should be able to use any HTML form input element, but I can't figure out the Altair class that would map to that.


Solution

  • This is possible, but somewhat difficult for two reasons:

    • although Vega supports arbitrary arguments to form inputs, vega-lite's schema prohibits such arguments. This means you need to work around Altair's normal validation mechanisms to use it.
    • The <datalist> must be injected into the chart's HTML output, and there's not a great mechanism for doing this.

    Here is an example of how you can work around these limitations and use a datalist within an Altair selection input binding:

    from IPython.display import HTML, display
    
    import altair as alt
    from vega_datasets import data
    
    from altair.utils.display import HTMLRenderer
    from altair.utils import schemapi
    
    datalist = """
    <datalist id="origin">
      <option value="USA">
      <option value="Europe">
      <option value="Japan">
    </datalist>
    """
    
    # Allow specifications that are invalid according to the schema.
    # This prevents a validation error for the `list` argument below.
    schemapi.DEBUG_MODE = False
    # `list` here should match the ID of the <datalist> specification.
    widget = alt.binding(input='text', name='Country', list='origin')
    
    # now create the chart as normal:
    selection = alt.selection_single(fields=['Origin'], bind=widget)
    color = alt.condition(selection,
                        alt.Color('Origin:N', legend=None),
                        alt.value('lightgray'))
    chart = alt.Chart(data.cars.url).mark_point().encode(
        x='Horsepower:Q',
        y='Miles_per_Gallon:Q',
        color=color,
        tooltip='Name:N'
    ).add_selection(
        selection
    )
    
    # Note the following assumes the default renderer.
    alt.renderers.enable('default')
    
    # Render the chart to HTML without validating it against the schema:
    renderer = alt.renderers.get()
    html = renderer(chart.to_dict(validate=False))['text/html']
    
    # Now display the datalist and chart rendering:
    display(HTML(datalist + html))
    

    enter image description here