Search code examples
pythonbokeh

How do I pre-select rows in a Bokeh.widget.DataTable?


Bokeh has the ability to display data in a dataframe as shown here:

http://docs.bokeh.org/en/latest/docs/user_guide/interaction/widgets.html#data-table

The Setup:

I have a dataframe of the following format:

Index|Location|Value
-----|--------|-----
1    |1       | 10
2    |1       | 20
3    |1       | 30
4    |2       | 20
5    |2       | 30
6    |2       | 40

This dataframe can be displayed in a data table like so:

source = ColumnDataSource(data={
    LOCATION_NAME: [],
    VALUE_NAME: []
})

columns = [
    TableColumn(field=LOCATION_NAME, title=LOCATION_NAME),
    TableColumn(field=VALUE_NAME, title=VALUE_NAME)
]

data_table = DataTable(source=source, columns=columns, width=400, height=800)

def update_dt(df):
    """Update the data table. This function is called upon some trigger"""
    source.data = {
        LOCATION_NAME: mt_val_df[LOCATION_NAME],
        VALUE_NAME: mt_val_df[VALUE]}

Ideally, I want this datatable to drive a heatmap where selections made for each location will lead to a changed value in the heatmap. But a heatmap cannot have several values for one location. I also do not know how to pre-select items in a datatable.

Assume that I have a second dataframe:

Index|Location|Value
-----|--------|-----
2    |1       | 20
6    |2       | 40

This dataframe represents a subset of the above table - perhaps some custom selection of the above.

The Problem:

At the most basic level: I have the index of my selection of rows. How can I highlight/pre-select rows in the data table above based on the rows of the second dataframe?

Update (2017-07-14): So far I tried setting the selected index on the data source python side. Although source['selected']['1d'].indices = [LIST OF MY SELECTION] does correctly set the indices, I am not seeing a corresponding update on the front-end DataTable in Bokeh 0.12.5.

I have also tried setting the indices on the front-end. My problem there is I don't know how to pass in parameters via CustomJS that are not related to Bokeh.

At a more complete level: How can selections in the datatable drive the heatmap?

Update (2017-07-17): I have not gotten the proposed solution to work within the context of a Bokeh app! I am currently trying to find the cause but it's a bit tricky to follow why nothing gets selected in the end. My suspicion is that the code string gets instantiated in the beginning when the page loads. My coordinates, however, are not calculated until later. Therefore, hitting a button with the callback leads to the selection of nothing - even if later the row selection has been calculated. Continued help would be appreciated!


Solution

  • I have found a partial answer to the above questions thanks to the helpful comments of Claire Tang and Bryan Van de ven here.

    Concerning Pre-selection not showing up on the DataTable

    This turns out to be caused be two issues as far as I am aware.

    1.) If I updated the selected index list in a CustomJS, I was missing to register the changes in the DataTable.

    button.callback = CustomJS(args=dict(source2=source2), code="""
    source2.selected['1d'].indices = [1,2,3];
    //I did not "emit" my changed selection in the line below.
    source2.properties.selected.change.emit();
    console.log(source2)
    """)  
    

    2.) The other important aspect to note is that I was on Bokeh version 0.12.5. In this version "source2.properties.selected" is an unknown property (perhaps because this function is located somehwhere else or not implemented yet). Therefore, selection also failed for me as long as I remained on Bokeh 0.12.5. An update to Bokeh 0.12.6 and the above line enabled selections to appear on the DataTable.

    Concerning dynamic input from a Jupyter Notebook

    The above example shows how I can use a button and a linked CustomJS callback to trigger selection of hard-coded lists. The question is how to feed in a list of index values based on some more dynamic calculations because CustomJS does not allow for external parameters that are not Bokeh related. On this topic, since CustomJS "code" attribute just takes a string. I tried the following:

    dynamically_calculated_index = [1,2,3]
    button.callback = CustomJS(args=dict(source1=source1, source2=source2), code="""
    source2.selected['1d'].indices = {0};
    source2.properties.selected.change.emit();
    console.log(source2)
    """.format(dynamically_calculated_index)) 
    

    I am not sure if this is best practice, and so I welcome feedback in this regard, but this works for me for now. As pointed out by @DuCorey, once these changes are in the main branch of bokeh, some permutations of my issue could be more easily solved as described by him/her.

    Also: This approach only works for a Jupyter Notebook where the entire cell gets recomputed again, and any pre-computed selected indices get bound at cell execution time. I can add a static list and it works for that, but if I want to dynamically calculate the above list, it will not work. I need to find a workaround still.

    Solving the above issues now allows me to concentrate on propagating changes in what is selected to a heatmap.

    Final answer using Bokeh server

    The answer here was rather simple: It is possible to change the selected items, but it has to be done in the following way:

    ds.selected = {'0d': {'glyph': None, 'indicices': []},
                   '1d': {indices': selected_index_list},
                   '2d': {}}
    

    Previously, I had only tried to replace the 1d indices, but for some unknown reason, I have to actually replace the entire selected dictionary for the change in selected index to be registered by the bokeh app. So don't just do:

    ds.selected['1d']['indices'] = selected_index_list
    

    This now works for me. An explanation from someone more knowledgable would be appreciated though.