Taptool with widgets. Select and Unselect of Taptool behave unexpectedly

Hello, I am plotting a simple dataset as below and want to use a taptool to select data. Later I plan to assign something (a picture to display for example) to the selected marker. I also have two widgets to change the plot axes.
When the widgets are in the initial state, plot appears as it suppose to be. Taptool correctly selects the data points. When I change an axis or axes from the widgets, and I select a point, data points on the graph replot as in the initial state! When I unselect the point, data points go back to correct data points from the widget. I tried this with customJS as well as pure python same results I obtained. am I missing something?
I run: Bokeh server version 3.5.0 (running on Tornado 6.4.1)
Below is the complete code:

from bokeh.models import ColumnDataSource, Select,  TapTool
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column, row

data = {
    'Formation Pres., psia': [9, 18, 26, 36, 45],
    'Pstar, psia': [10, 20, 30, 40, 50],
    'Probe MD, m': [5, 4, 3, 2, 1],
    'Probe TVD, m': [7, 3, 4, 1, 3],
    'DD Mobility, mD/cP': [2, 4, 6, 8, 10],
    'color': ['red', 'green', 'blue', 'orange', 'purple'],
    'Size': [10, 20, 30, 40, 50],
    'legend': ['A', 'B', 'C', 'D', 'E'],
    'marker': ['circle', 'triangle', 'square', 'diamond', 'star']
}

XYlist = ['DD Mobility, mD/cP', 'Formation Pres., psia', 'Probe MD, m', 'Probe TVD, m', 'Pstar, psia']
X = Select(value='Pstar, psia', options=list(XYlist), title="X Axis")
Y = Select(value='Probe MD, m', options=list(XYlist), title="Y Axis")

src = ColumnDataSource(data=data)

def make_plot(src, xx, yy):
    p = figure(tools=["save", "pan", "box_zoom", "reset", "wheel_zoom"], width=400, height=400)
    renderer = p.scatter(x=xx, y=yy, alpha=1.0, source=src, fill_alpha=0.5, size='Size', legend_field='legend', marker='marker', color='color', line_color='color', line_width=1.1,
                        selection_color="color", selection_fill_alpha=0.6, selection_line_color="#580F41", selection_line_alpha=1.0, selection_line_width=3.0,
                        nonselection_color="color", nonselection_fill_alpha=0.6, nonselection_line_color="color", nonselection_line_alpha=1.0, nonselection_line_width=1.2)

    # Set initial axis labels
    p.xaxis.axis_label = xx
    p.yaxis.axis_label = yy
    
    # Add TapTool
    tap_tool = TapTool(renderers=[renderer])
    p.add_tools(tap_tool)
    
    return p, renderer

# Create the initial plot
p, renderer = make_plot(src, X.value, Y.value)

def update_plot(attr, old, new):
    renderer.glyph.x = X.value
    renderer.glyph.y = Y.value
    p.xaxis.axis_label = X.value
    p.yaxis.axis_label = Y.value

X.on_change('value', update_plot)
Y.on_change('value', update_plot)

layout_tab = column(row(X, Y), p)
curdoc().add_root(layout_tab) 

That’s really odd behavior, and I don’t have any immediate explanation or theories. [1] All I can suggest is filing a GitHub Issue with full details.


  1. Changing glyphs’ .x and .y fields is not actually all that common in my estimation, users tend to update the data in the data source instead. The latter case is very well established and tested, but the former, I suppose is not. ↩︎

Yes obviously CDS update solution as below is ok to work with…

from bokeh.models import ColumnDataSource, Select, TapTool
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column, row

data = {
    'Formation Pres., psia': [9, 18, 26, 36, 45],
    'Pstar, psia': [10, 20, 30, 40, 50],
    'Probe MD, m': [5, 4, 3, 2, 1],
    'Probe TVD, m': [7, 3, 4, 1, 3],
    'DD Mobility, mD/cP': [2, 4, 6, 8, 10],
    'color': ['red', 'green', 'blue', 'orange', 'purple'],
    'Size': [10, 20, 30, 40, 50],
    'legend': ['A', 'B', 'C', 'D', 'E'],
    'marker': ['circle', 'triangle', 'square', 'diamond', 'star']
}

XYlist = [
    'DD Mobility, mD/cP',
    'Formation Pres., psia',
    'Probe MD, m',
    'Probe TVD, m',
    'Pstar, psia'
]

# Create Select widgets for X and Y axes
X = Select(value='Pstar, psia', options=XYlist, title="X Axis")
Y = Select(value='Probe MD, m', options=XYlist, title="Y Axis")

# Original data source
src = ColumnDataSource(data=data)


def get_filtered_data(x_axis, y_axis):
    # Start by copying all data from the original source
    filtered_data = {key: value for key, value in src.data.items()}
    
    # Update the 'x' and 'y' columns based on the current selections
    filtered_data['x'] = src.data[x_axis]
    filtered_data['y'] = src.data[y_axis]
    
    return filtered_data

# Initialize filtered_src with the initial widget selections
filtered_src = ColumnDataSource(data=get_filtered_data(X.value, Y.value))

def make_plot():
    p = figure(tools=["save", "pan", "box_zoom", "reset", "wheel_zoom"], width=600, height=600,title="Interactive Scatter Plot")
    
    # Create the scatter glyph using 'x' and 'y' columns from filtered_src
    filtered_renderer = p.scatter(x='x', y='y',alpha=1.0, source=filtered_src,fill_alpha=0.5, size='Size', legend_field='legend', marker='marker', color='color', line_color='color', line_width=1.1,
                        selection_color="color", selection_fill_alpha=0.6, selection_line_color="#580F41", selection_line_alpha=1.0, selection_line_width=3.0,
                        nonselection_color="color", nonselection_fill_alpha=0.6, nonselection_line_color="color", nonselection_line_alpha=1.0, nonselection_line_width=1.2)
    
    # Set initial axis labels
    p.xaxis.axis_label = X.value
    p.yaxis.axis_label = Y.value
    
    # Add TapTool for the filtered renderer
    tap_tool = TapTool(renderers=[filtered_renderer])
    p.add_tools(tap_tool)
    
    return p, filtered_renderer

# Create the plot
p, filtered_renderer = make_plot()

def update_plot(attr, old, new):
    # Get the new axis selections
    new_x = X.value
    new_y = Y.value
    
    # Update the axis labels
    p.xaxis.axis_label = new_x
    p.yaxis.axis_label = new_y
    
    # Update the 'x' and 'y' data in filtered_src based on new selections
    new_data = get_filtered_data(new_x, new_y)
    filtered_src.data = new_data
    
    
    # Update x-axis and y-axis ranges with +/-10% padding
    x_min, x_max = min(new_data['x']), max(new_data['x'])
    y_min, y_max = min(new_data['y']), max(new_data['y'])
    
    x_padding = (x_max - x_min) * 0.1
    y_padding = (y_max - y_min) * 0.1
    
    p.x_range.start = x_min - x_padding
    p.x_range.end = x_max + x_padding
    p.y_range.start = y_min - y_padding
    p.y_range.end = y_max + y_padding

# Attach the update_plot callback to the Select widgets
X.on_change('value', update_plot)
Y.on_change('value', update_plot)

# Layout the widgets and plot
layout = column(row(X, Y), p)

# Add the layout to the current document
curdoc().add_root(layout)
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.