I have a univariate function that defines a performance measure as a function of an operating point. As part of visualization and dynamic specification of the operating point, I am attempting to drag a marker along the curve.
An attempt at uses the PointDrawTool. Several unexpected behaviors have been encountered, and help on how to accomplish more intuitively and robustly is appreciated. An example python file using the bokeh server model is attached to support the discussion.
Issues:
1b. For some performance functions, the dragging along the curve is smooth and generally intuitive from a user perspective (lefthand plot/function in the attached example).
1b. For other performance functions, the dragging along the curve is counterintuitive, with marker/circle moving opposite the expected mouse motions although it does remain on the constraining function.
2a. A secondary interactive feature to include, is clicking on the plot to have the operating point jump to the x-axis location and the corresponding y-axis location equal the performance measure function at that location. This is attempted using the num_objects=1 setting in the PointDrawTool.
This doesn’t appear to work either. A marker does show up in the expected location with this action (expected), but the original point remains and generally moves around (unexpected).
2b. The point-draw tool doesn’t seem to support column data sources based on pandas dataframes, but rather only works with dictionaries. This seems incosistent with other glyphs and models. This is not a functional limitation for the current use case, just a noted difference.
Thanks in advance for any help.
EXAMPLE
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""
import numpy as np
import pandas as pd
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row
from bokeh.models import ColumnDataSource
from bokeh.models import Line, Circle
from bokeh.models import PointDrawTool, HoverTool
from bokeh.models.transforms import LinearInterpolator
from bokeh.transform import transform
from dataclasses import dataclass
@dataclass
class DS:
data : pd.DataFrame
source : ColumnDataSource
@dataclass
class UI:
plot : figure
curve : Line
r_sel : Circle
li_x : LinearInterpolator
li_y : LinearInterpolator
tr_x : transform
tr_y : transform
drawtool: PointDrawTool
hover : HoverTool
op = np.linspace(start=0.000, stop=1.000, num=1001)
fA = (2.0/np.pi) * np.arctan(10.0*op)
fB = np.exp(-np.pi*(op ** 2))
_data = pd.DataFrame(np.column_stack((op,fA,fB)), columns=('op','fA','fB'))
_source = ColumnDataSource(_data)
ds = DS(data=_data, source=_source)
uis = []
mdls = []
for f in ('fA','fB',):
# Performance curve
p = figure(plot_width=500, plot_height=500, x_range=(0.0,1.0), y_range=(0.0,1.0))
ln = p.line(x='op', y=f, source=ds.source, line_color='#4292c6')
# Selected operating point along performance curve
li_x = LinearInterpolator(x='op', y='op', data=ds.source, clip=False)
li_y = LinearInterpolator(x='op', y=f , data=ds.source, clip=False)
tr_x = transform('op', li_x)
tr_y = transform('op', li_y)
_opd = {'op': [ds.data.op[500]]}
_ops = ColumnDataSource(_opd)
r_sel = p.circle(x='op', y=tr_y, source=_ops, color='#000000', size=12, name='op_'+f)
drawtool = PointDrawTool(renderers=[r_sel], add=True, num_objects=1)
p.add_tools(drawtool)
p.toolbar.active_tap = drawtool
h = HoverTool(names=['op_'+f],
tooltips=[('Operating Point', '$x{%5.3f}'),(f, '$y{%5.3f}')],
formatters={'$x': 'printf', '$y': 'printf'},
mode='vline')
p.add_tools(h)
_ui = UI(plot=p, curve=ln, r_sel=r_sel,
li_x=li_x, li_y=li_y, tr_x=tr_x, tr_y=tr_y,
drawtool=drawtool, hover=h)
uis += [_ui]
mdls += [_ui.plot]
curdoc().add_root(row((mdls)))