Our users would like a show annotations by selected graph points and showing the X & Y values. See the picture below.
Can anyone suggest a way to do this?
We already support tooltips when hovering over a point but the users want the annotations to be shown for several points at once and be fixed so they can export a picture for a report.
As far as I can tell this is not yet implemented in Bokeh but there are several discussions about it on Github. See here and here for example. There are some workarounds mentioned in these issues that you can take a look.
AFAIK making the existing persistent tooltip capability more accessible to use (e.g. data-driven, etc.) is part of the currently funded CZI: R6 work. This is the relevant issue:
@Bryan I took your example from one of the pages @nmasnadi pointed to. It works OK with the circle example you give. And also with scatter, but it doesn’t seem to work with line (unless the point you need to click is really hard to locate and I haven’t managed it). Is there a way to get it to work for line?
from bokeh.layouts import column
from bokeh.plotting import figure, curdoc
from bokeh.models.sources import ColumnDataSource
from bokeh.models import CustomJS, LabelSet, TapTool
p = figure()
points = ColumnDataSource(data=dict(x=[1,2,3,4,5], y=[2,6,3,9,7], extra=list("abcde")))
#p.circle(x='x', y='y', source=points, size=20, alpha=0.5) # works
#p.line(x='x', y='y', source=points) # does not work
p.scatter(x='x', y='y', source=points) # works
labels = ColumnDataSource(data=dict(x=[], y=[], t=[], ind=[]))
p.add_layout(LabelSet(x='x', y='y', text='t', y_offset=10, x_offset=10, source=labels))
code = """
const data = labels.data
for (let i=0; i<points.selected.indices.length; i++) {
const ind = points.selected.indices[i]
data.x.push(points.data.x[ind])
data.y.push(points.data.y[ind])
data.t.push(points.data.extra[ind])
data.ind.push(ind)
}
labels.change.emit()
"""
callback=CustomJS(args=dict(points=points, labels=labels), code=code)
p.add_tools(TapTool(callback=callback))
# add column - need to run this with bokeh serve --show
curdoc().add_root(column(p))
Lines have a different, connected topology. In particular there are fewer segments in between the points, than there are points. Because of this, line selections are recorded separately in selected.line_indices. Point hit-testing against lines (i.e. what the TapTool does) returns the index the closest line segment to the tap or click, and the index of a line segment is the index of the “first” of the two points that define the line segment.
I think what @Bryan suggested works. You just need to replace points.selected.indices with points.selected.line_indices in your code in two places (in the for loop definition and the first line in the for loop).
Yes I’ve tried that but it works as he describes: it selects a line segment and gives you the point at the start of the segment. So if you click the point you want, slightly to the right you get the point you want, slightly to the left you get the point at the start of the segment, which isn’t very useful.
Point hit-testing for line glyphs only checks against the line segments. If you want point hit-testing for the points of a line rather than the segments, then you will need to add a scatter plot (ideally using the same CDS to avoid duplicating data) and point hit-test against that instead of the line glyph. You should be able to set the alpha values for the scatter to zero, will skip the actual HTML context API calls.