Trying to use PointDrawTool and PolyDrawTool together

Hi, thanks in advance for looking at my code below. I can create points and a line but I would like to connect a line between 2 points and make it draggable with first point being fixed. Also would like to add a hover listener so when I hover over a point I can get the coordinates. Thanks again

here is my py code

from bokeh.plotting import figure, output_file, show, Column
from bokeh.models import DataTable, TableColumn, PointDrawTool, ColumnDataSource
from bokeh.models import PolyDrawTool

output_file("tools_point_draw.html")

p = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
           title='Point Draw Tool')
p.background_fill_color = 'lightgrey'

source = ColumnDataSource({
    'x': [1, 5, 9], 'y': [1, 5, 9], 'color': ['red', 'green', 'yellow']
})

renderer = p.scatter(x='x', y='y', source=source, color='color', size=10)
columns = [TableColumn(field="x", title="x"),
           TableColumn(field="y", title="y"),
           TableColumn(field='color', title='color')]
table = DataTable(source=source, columns=columns, editable=True, height=200)
  
l1 = p.multi_line([[1, 9]], [[5, 5]], line_width=5, alpha=0.4, color='red')

draw_tool = PointDrawTool(renderers=[renderer], empty_value='black')
draw_tool_l1 = PolyDrawTool(renderers=[l1])
p.add_tools(draw_tool, draw_tool_l1)
p.toolbar.active_tap = draw_tool

show(Column(p, table))

AFAIK the draw/edit tools always are able to act on all points, and there is no way to “fix” any of them but cc @Philipp_Rudiger in case there developments I missed.

If I double click on a point and then move mouse to another point then single click then press escape, I can see my line between 2 points. I am trying to capture both point coordinates into variables for further use. Maybe I dont need hover for this because my program already shows the points in a table after clicking. Just need to get them into variables when I create the line, thanks

The coordinates will be in the CDS data source for the renderer. The edit/draw tools update the backing data sources to accomplish their work.

thanks for the answer but I am really new to Bokeh. If I move a point my table shows the new coordinates. I think I need a callback to capture the new coordinates into x and y variables. I want to do some math on my new variable objects. I am unclear on how to grab the coordinates

First thing first: are you expecting to use Python variables? As you are currently using it, Bokeh is generating HTML/JS output and you’d only be able to access things from JavaScript. Is that your expectation?

yes I need python variables in my javascript

ps. I have a similar model in matplotlib where I use pyscript (python vars in js)
but since matplotlib is not interactive and displays as png file I am thus using an equivalent in Bokeh for the interactivity. Problem is that I am very new to all this including Python so am muddling around. Thanks for your help

I have made some progress on my callback but this code below returns coordinates in the console when tapping on a point. I have tried without success to create a global variable for x and y to be used outside of my callback

from bokeh.models import ColumnDataSource, TapTool, CustomJS
from bokeh.plotting import figure, output_file, show

output_file("openurl.html")

p = figure(width=400, height=400,
             tools="tap", title="Click the Dots")

source = ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y=[2, 5, 8, 2, 7]))

p.circle('x', 'y', color='green', size=10, source=source)

callback = CustomJS(args=dict(source=source), code="""
    var selectedIndex = source.selected.indices;
    for (var i = 0; i < selectedIndex.length; i++) {
        console.log("x:", source.data['x'][selectedIndex[i]]);
        console.log("y:", source.data['y'][selectedIndex[i]]);
           }
""")

taptool = p.select(type=TapTool)
source.selected.js_on_change('indices', callback)
show(p)

What does this mean, exactly? In other Bokeh callbacks? Or in completely non-BokehJS external JavaScript code?

I want to be able to grab the coordinates of the point from the event and display results like an annotation right below the graph

I don’t see why you would need any global storage then. You can add a Bokeh Div below the plot, and pass the Div to the same CustomJS callback to update all in one place.

Here is a simplified example that just shows the indices but it should be straightforward to update it to generate text with the x and y coordinates in whatever form you need them to appear:

from bokeh.models import ColumnDataSource, Div, CustomJS
from bokeh.plotting import column, figure, show

p = figure(tools="tap")

source = ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y=[2, 5, 8, 2, 7]))

p.circle('x', 'y', color='green', size=40, source=source)

div = Div()

callback = CustomJS(args=dict(source=source, div=div), code="""
    div.text = `Selected indices: ${source.selected.indices}`
""")

source.selected.js_on_change('indices', callback)

show(column(p, div))

ok thanks very much for the good advice, I was able to get both point coordinates in a div below the graph.

1 Like