Search code examples
javascriptpython-3.xdebuggingcallbackbokeh

Python Bokeh CustomJS: Debugging a JavaScript callback for the Taping-Tool


I am working with Python 3.6.2 and Bokeh 1.0.4 to create a custom JavaScript callback in my plot.

By tapping on one of the points in the plot, I'd like all points sharing the same attribute in the id-column to be highlighted.

Iterating over all datapoints with JavaScript and manipulating the respective 'selected'-attribute in the ColumnDataSource-object should do the trick. Unfortunately I can not figure out how to correct this code.

# Import packages
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool


# Create the data for the points
x = [0, 1, 2, 3]
y = [0, 1, 0, 1]
ids = ['A','B','A','B']

data = {'x':x, 'y':y, 'id':ids}
source = ColumnDataSource(data)


# Add tools to the plot
tap = TapTool()
hover = HoverTool(tooltips=[("X", "@x"),
                            ("Y", "@y"),
                            ("ID", "@id")])


# Create a plotting figure
p = figure(plot_width=400, plot_height=400, tools=[tap,hover])


# Code for the callback
code = """
// Set column name to select similar glyphs
var column = 'id';

// Get data from ColumnDataSource
var data = source.data;

// Get indices array of all selected items
var selected = source.selected.indices;

// Array to store glyph-indices to highlight
var select_inds = [];


// Check if only a single glyph is selected
if(selected.length==1){

    // Get the value of the column to find similar attributes/glyphs
    attribute_value = data[column][selected[0]];

    // Iterate over all entries in the ColumnDataSource
    for (var i=0; i<data[column].length; ++i){

        // Check if items have the same attribute
        if(data[column][i]==attribute_value){

            // Add index to selected list
            select_inds.push(i);
            }
        }
    }

// Set selected glyphs in ColumnDataSource
source.selected.indices = select_inds;

// Save changes to ColumnDataSource
source.change.emit();
"""


# Create a CustomJS callback with the code and the data
callback = CustomJS(args={'source':source}, code=code)

# Add the callback to the ColumnDataSource
source.callback=callback


# Plots circles
p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)

# Show plot
show(p)

An older version of this problem with Bokeh 0.13.0 could not solve my problem.


Solution

  • You were almost there! The callback has to be added to the TapTool instead of the ColumnDataSource.

    # Import packages
    from bokeh.plotting import figure, show
    from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool
    
    
    # Create the data for the points
    x = [0, 1, 2, 3]
    y = [0, 1, 0, 1]
    ids = ['A','B','A','B']
    
    # Generate data source for the visualization
    data = {'x':x, 'y':y, 'id':ids}
    source = ColumnDataSource(data)
    
    
    # Add tools to the plot
    tap = TapTool()
    hover = HoverTool(tooltips=[("X", "@x"),
                                ("Y", "@y"),
                                ("ID", "@id")])
    
    
    # Create a plotting figure
    p = figure(plot_width=400, plot_height=400, tools=[tap,hover])
    
    
    # Code for the callback
    code = """
    // Set column name to select similar glyphs
    var column = 'id';
    
    // Get data from ColumnDataSource
    var data = source.data;
    
    // Get indices array of all selected items
    var selected = source.selected.indices;
    
    // Array to store glyph-indices to highlight
    var select_inds = [];
    
    // Check if only a single glyph is selected
    if(selected.length==1){
    
        // Get the value of the column to find similar attributes/glyphs
        var attribute_value = data[column][selected[0]];
    
        // Iterate over all entries in the ColumnDataSource
        for (var i=0; i<data[column].length; ++i){
    
            // Check if items have the same attribute
            if(data[column][i]==attribute_value){
    
                // Add index to selected list
                select_inds.push(i);
                }
            }
        }
    
    // Set selected glyphs in ColumnDataSource
    source.selected.indices = select_inds;
    
    // Save changes to ColumnDataSource
    source.change.emit();
    """
    
    
    # Create a CustomJS callback with the code and the data
    callback = CustomJS(args={'source':source}, code=code)
    
    # Add the callback to the taptool
    tap.callback=callback
    
    
    # Plots circles
    p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)
    
    # Show plot
    show(p)