@RickD If I understand your question correctly then the below code is one idea of how to solve it.
I use a CDS that defines the 2 set of coordinates of points that define the points (circle) and line. I use line
and circle
glyphs and both use the same CDS. Use the tool PointDrawTool
which is associated with the circle
glyph. Hence when you move a point (circle) then the CDS data is automatically updated and both circle and line is updated.
Have added a Div
to display slope and angle. I use a CustomJS
to update the text
property of the Div
with new angle and slope when data
of the CDS is changed.
I did not find any argument for PointDrawTool
that can prevent deletion of a point. So I have a dummy CDS to keep a copy of the points.
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, Div, CustomJS, PointDrawTool
from bokeh.layouts import column
from bokeh.io import output_file, save
from math import atan2, degrees
output_file('drag_point.html')
source = ColumnDataSource(
data = {
'x': [1,8],
'y': [2,7]
}
)
# using an extra CDS - did not find any argument to prevent deletion of a point
src_copy = ColumnDataSource(data = {
'x': [1,8],
'y': [2,7]
})
p = figure(
x_range=(0, 10),
y_range=(0, 10),
width = 400,
height = 400,
tools = []
)
r_c = p.circle(
x = 'x',
y = 'y',
size = 20,
fill_color = 'red',
line_color = 'black',
alpha = 0.7,
source = source
)
r_l = p.line(
x = 'x',
y = 'y',
line_color = 'blue',
line_width = 3,
line_alpha = 0.7,
source = source
)
tool = PointDrawTool(renderers=[r_c], add = False)
p.add_tools(tool)
p.toolbar.active_tap = tool
dx = source.data['x'][1]-source.data['x'][0]
dy = source.data['y'][1]-source.data['y'][0]
slope = dy/dx
angle = degrees(atan2(dy, dx))
div = Div(text = f'slope: {slope:.2f}, angle (deg): {angle:.2f}')
cb = CustomJS(
args = {
'src': source,
'src_copy': src_copy,
'div': div
},
code = '''
// did not find any argument to prevent delete of a point
if (src.data.x.length === 1) {
src.data = {
'x': [src_copy.data.x[0], src_copy.data.x[1]],
'y': [src_copy.data.y[0], src_copy.data.y[1]],
}
return;
}
const dx = src.data.x[1] - src.data.x[0];
const dy = src.data.y[1] - src.data.y[0];
const slope = dx/dy;
const angle = (180/Math.PI)*(Math.atan2(dy, dx));
div.text = `slope: ${slope.toFixed(2)}, angle (deg): ${angle.toFixed(2)}`;
src_copy.data = {
'x': [src.data.x[0], src.data.x[1]],
'y': [src.data.y[0], src.data.y[1]],
}
'''
)
source.js_on_change('data', cb)
save(column(p, div))