Search code examples
javascriptpythonbokeh

Select rows from ColumnDataSource in Bokeh interactive line plot


I am stuck trying to select rows to plot in a simple Bokeh line plot. The desired result is a simple line plot with Date on the x-axis and Value on the y-axis. Using 2 select widgets I would like to select Country and Type.

Any suggestions a more than welcome!

My code so far:

import pandas as pd
import numpy as np
from bokeh.models.widgets import Select
from bokeh.models import ColumnDataSource, Select, CDSView, GroupFilter
from bokeh.io import show, output_notebook
from bokeh.plotting import figure

output_notebook()

# base
df = pd.DataFrame({'Country': ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B'],
                   'Date': ['01-01-2020', '01-02-2020', '01-03-2020', '01-01-2020', '01-02-2020', '01-03-2020', '01-01-2020', '01-02-2020', '01-03-2020', '01-01-2020', '01-02-2020', '01-03-2020'],
                   'Type': ['X', 'X', 'X', 'Y', 'Y', 'Y', 'X', 'X', 'X', 'Y', 'Y', 'Y'],
                   'Value': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]})
df['Date'] = pd.to_datetime(df['Date'])

source = ColumnDataSource(df)

country_filter = CDSView(source=source, filters=[GroupFilter(column_name='Country', group='A')])
type_filter = CDSView(source=source, filters=[GroupFilter(column_name='Type', group='X')])

country_select = Select(title="Country:", value="A", options=np.unique(source.data['Country']).tolist())
country_select.js_link('value', country_filter, 'group')

type_select = Select(title="Type:", value="X", options=np.unique(source.data['Type']).tolist())
type_select.js_link('value', type_filter, 'group')

p = figure()
p.line(x='Date', y='Value', source=source, view=view)

layout = row(p, column(country_select, type_select))

show(layout)

Solution

  • You almost got it. Here's a working version. Two things to note:

    1. I had to manually link group filters' changes to the view's change signal - right now Bokeh doesn't make that link internally
    2. Since you're using lines, this code will produce a warning about using filters with glyphs with connected topology. It can safely be ignored in your case
    import numpy as np
    import pandas as pd
    from bokeh.io import show
    from bokeh.layouts import row, column
    from bokeh.models import ColumnDataSource, Select, CDSView, GroupFilter, CustomJS
    from bokeh.plotting import figure
    
    df = pd.DataFrame({'Country': ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B'],
                       'Date': ['01-01-2020', '01-02-2020', '01-03-2020', '01-01-2020', '01-02-2020', '01-03-2020',
                                '01-01-2020', '01-02-2020', '01-03-2020', '01-01-2020', '01-02-2020', '01-03-2020'],
                       'Type': ['X', 'X', 'X', 'Y', 'Y', 'Y', 'X', 'X', 'X', 'Y', 'Y', 'Y'],
                       'Value': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]})
    df['Date'] = pd.to_datetime(df['Date'])
    
    source = ColumnDataSource(df)
    
    country_filter = GroupFilter(column_name='Country', group='A')
    type_filter = GroupFilter(column_name='Type', group='X')
    view = CDSView(source=source, filters=[country_filter, type_filter])
    
    # Alas, we need this manual step to make the view know about the filters' changes.
    for f in view.filters:
        f.js_on_change('group', CustomJS(args=dict(view=view),
                                         code="view.properties.filters.change.emit();"))
    
    country_select = Select(title="Country:", value="A", options=np.unique(source.data['Country']).tolist())
    country_select.js_link('value', country_filter, 'group')
    
    type_select = Select(title="Type:", value="X", options=np.unique(source.data['Type']).tolist())
    type_select.js_link('value', type_filter, 'group')
    
    p = figure()
    p.line(x='Date', y='Value', source=source, view=view)
    
    show(row(p, column(country_select, type_select)))