0

This is part of a series of questions related to this project: I have a dataset with 5000+ (x,y) coordinates divvied up into 1300+ groups. Using bokeh and its HoverTool function, I can plot the points and make it such that, when I hover over a given point, line segments appear between that point and every other point in that group. However, while I'm passable in python, I know precisely zero JavaScript.

TapTool Interaction with CustomJS

In addition to appearing when I hover over point, if I click on a point, I want those line segments to remain until I click somewhere else.

I know that bokeh has the TapTool, and I'm reasonably certain that, natively, the HoverTool and the TapTool can coexist quite happily. However, since the HoverTool functionality is implemented in JavaScript, I think that the TapTool functionality needs to be similarly constructed (though I'm happy to be proven wrong).

Here's what I've got so far:

# import packages import pandas as pd from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool from bokeh.plotting import figure, output_file, show # Read in the DataFrame data = pd.read_csv("sanitized_data.csv") # Generate (x, y) data from DataFrame x = list(data["X"]) y = list(data["Y"]) # Generate dictionary of links; for each Col1, I want # there to be a link between each Col3 and every other Col3 in the same Col1 links = {data["Col2"].index[index]:list(data[(data["Col1"]==data["Col1"][index]) & (data["Col2"]!=data["Col2"][index])].index) for index in data["Col2"].index} # bokeh ColumnDataSource1 = (x, y) placeholder coordinates for start and end points of each link source1 = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []}) # bokeh ColumnDataSource2 = DataFrame, itself source2 = ColumnDataSource(data) # bokeh ColumnDataSource3 = associating Col2, Col1, and (x, y) coordinates source3 = ColumnDataSource({"x":x,"y":y,'Col2':list(data['Col2']),'Col1':list(data['Col1'])}) # Set up Graph and Output file output_file("sanitized_example.html") p = figure(width=1900, height=700) # Set up line segment and circle frameworks sr = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='red', alpha=0.4, line_width=.5, source=source1, ) cr = p.circle(x='x', y='y', color='blue', size=5, alpha=1, hover_color='orange', hover_alpha=1.0, source=source3) # Add JS string for HoverTool Callback code = """ const links = %s const data = {'x0': [], 'y0': [], 'x1': [], 'y1': []} const indices = cb_data.index.indices for (let i = 0; i < indices.length; i++) { const start = indices[i] for (let j = 0; j < links[start].length; j++) { const end = links[start][j] data['x0'].push(circle.data.x[start]) data['y0'].push(circle.data.y[start]) data['x1'].push(circle.data.x[end]) data['y1'].push(circle.data.y[end]) } } segment.data = data """ % links # Add JS string for DataTable populating # Establish Tooltips TOOLTIPS = [("Col2","@Col2"), ("Col1","@Col1")] # Create JavaScript Callbacks callback = CustomJS(args={'circle': cr.data_source, 'segment': sr.data_source}, code=code) # Add tools to the Graph p.add_tools(HoverTool(tooltips=TOOLTIPS, callback=callback, renderers=[cr])) p.add_tools(TapTool()) # Show the graph show(p) 

A dataset is available at the linked github page. Unfortunately, I don't even know where to start from here.

Edit: I found something! This SO post may have the answer I'm looking for.

1 Answer 1

0

Okay, a guy at work helped me out immensely: according to the documentation, when you use the HoverTool(callback) parameter, cb_data contains an index field. However, when you use the TapTool(callback) parameter, cb_data doesn't contain that field. Instead, the indices are accessed through the cb_obj object:

# import packages import pandas as pd from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool from bokeh.plotting import figure, output_file, show # Read in the DataFrame data = pd.read_csv("sanitized_data.csv") # Generate (x, y) data from DataFrame x = list(data["X"]) y = list(data["Y"]) # Generate dictionary of links; for each Col1, I want # there to be a link between each Col3 and every other Col3 in the same Col1 links = {data["Col2"].index[index]:list(data[(data["Col1"]==data["Col1"][index]) & (data["Col2"]!=data["Col2"][index])].index) for index in data["Col2"].index} links_list = [] for key in links.keys(): links_list.append(links[key]) # bokeh ColumnDataSource1 = (x, y) placeholder coordinates for start and end points of each link source1 = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []}) # bokeh ColumnDataSource2 = DataFrame, itself source2 = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []}) # bokeh ColumnDataSource3 = associating Col2, Col1, and (x, y) coordinates source3 = ColumnDataSource({"x":x,"y":y,'Col2':list(data['Col2']),'Col1':list(data['Col1']), 'Links':links_list}) # Set up Graph and Output file output_file("sanitized_example.html") p = figure(width=1900, height=700) # Set up line segment and circle frameworks sr1 = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='red', alpha=0.4, line_width=.5, source=source1) sr2 = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='red', alpha=0.4, line_width=.5, source=source2) cr = p.circle(x='x', y='y', color='blue', size=5, alpha=1, hover_color='orange', hover_alpha=1.0, source=source3) # Add JS string for HoverTool Callback code_for_hover = """ const links = %s const data = {'x0': [], 'y0': [], 'x1': [], 'y1': []} const indices = cb_data.index.indices for (let i = 0; i < indices.length; i++) { const start = indices[i] for (let j = 0; j < links[start].length; j++) { const end = links[start][j] data['x0'].push(circle.data.x[start]) data['y0'].push(circle.data.y[start]) data['x1'].push(circle.data.x[end]) data['y1'].push(circle.data.y[end]) } } segment.data = data """ % links # Add JS string for Tap segments code_for_tap = """ const links = %s; const data = {'x0': [], 'y0': [], 'x1': [], 'y1': []}; const indices = cb_obj.indices; for (let i = 0; i < indices.length; i++) { const start = indices[i]; for (let j = 0; j < links[start].length; j++) { const end = links[start][j]; data['x0'].push(circle.data.x[start]); data['y0'].push(circle.data.y[start]); data['x1'].push(circle.data.x[end]); data['y1'].push(circle.data.y[end]); } } segment.data = data; """ % links # Establish Tooltips TOOLTIPS = [("Col2","@Col2"), ("Col1","@Col1")] # Create JavaScript Callbacks callback_on_hover = CustomJS(args={'circle': cr.data_source, 'segment': sr1.data_source}, code=code_for_hover) callback_on_tap = CustomJS(args={'circle': cr.data_source, 'segment': sr2.data_source}, code=code_for_tap) # Add tools to the Graph p.add_tools(HoverTool(tooltips=TOOLTIPS, callback=callback_on_hover, renderers=[cr])) p.add_tools(TapTool()) source3.selected.js_on_change('indices', callback_on_tap) # Show the graph show(p) 

The only caveat is that it seems like I needed to have a duplicate ColumnDataSource so that the HoverTool and the TapTool can have unfettered access to the data without the other tool interfering.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.