Show values in legend

Things are going well with my plots. Up to now, I managed to sync the x-axis and show the vertical crosshair on each synced plot at the same position.

Is there a way to show the y-axis values of the current cursor position in the legend? The idea is to move the cursor and see all the values next to the legend_labels. This would be very nice, since the diagram is not affected by the overlapping information from the hover tool.

Here a screenshot with the values put in by hand (red font):

Short answer is yes it can be done, but it might not be a ā€œprecookedā€ feature for you. The way I’d go about it:

Build a CustomJS callback that…

  • Triggers on a MouseMove Event (bokeh.events — Bokeh 2.4.2 Documentation) over your plot
  • Retrieves the X coords of your where the mouse currently is
  • Goes through each datasource that’s driving your lines (are you using multiline or line?), and find the corresponding Y to that X (linear interp or find nearest or something)
  • Update the legend’s label with those values in the callback

This is a pretty ā€œmanual effortā€ approach not using anything built in though, and there might be a way to cook up leveraging the ā€˜mode=vline’ from the built in HoverTool (as it essentially does the same thing but deep in the JS backend I know nothing about)… so I’m not sure how you could access that result to update your legend via CustomJS callback. Interested to know if that is possible though.

If you cook up a minimal reproducible example and share it here I should have time to help you out.

Here’s my homebrew example:

from bokeh.models import CustomJS, Line, ColumnDataSource, Legend, LegendItem, Span
from bokeh.plotting import figure, save
from bokeh.events import MouseMove

data = {'x':[1,2,3,4,5,6,7],'y':[3,5,6,6,8,10,13]}
src = ColumnDataSource(data)
f = figure()
glyph = Line(x='x',y='y')
rend = f.add_glyph(src,glyph)
#create legend manually
leg_items = [LegendItem(label='Thing: ',renderers=[rend],index=0)]
leg = Legend(items=leg_items)
f.add_layout(leg)

#create spans to follow your mouse
vert = Span(location=0, dimension='height', line_color='black',line_dash='solid', line_width=1)
f.add_layout(vert)
horz = Span(location=0, dimension='width', line_color='black',line_dash='solid', line_width=1)
f.add_layout(horz)

#custom callback to trigger on mouse move, passing the things we need for it
#the legend items, the datasource, the base label, and the spans
cb = CustomJS(args=dict(leg_items=leg_items,src=src,lbl='Thing: ',vert=vert,horz=horz)
              ,code='''
              //update the spans to current mouse location
              vert.location = cb_obj.x
              horz.location = cb_obj.y
              var x = cb_obj.x
              //going through the length of your data source -1
              for (var i=0; i<src.data['x'].length-1;i++){
                      //if the mouse location x is between the source x and the next x...
                      if (x > src.data['x'][i] && x < src.data['x'][i+1]){
                              //linear interp math --> find in between the two x's
                              var m = (src.data['y'][i+1]-src.data['y'][i])/(src.data['x'][i+1]-src.data['x'][i])
                              var y = (m*(src.data['x'][i+1]-x)-src.data['y'][i+1])*-1
                              //update legend item label
                              leg_items[0].label = lbl+y.toFixed(2).toString()  
                              //once this is done no point in continuing loop so break it
                              break}                      
                      }
              ''')
#when user moves mouse over figure, trigger this callback
f.js_on_event(MouseMove,cb)
save(f,'dummy.html')

mousemove

Plenty of ā€œefficiencyā€/scaling to have with multiple legend items etc and/or if you’re using multiline but hopefully this is enough to set you on your way.

@Jan1 Just curious, what is the need to have this display in a legend instead of a hover tooltip? If a tooltip is an option then you can set the line_policy on the hover tool to "interp" and then I believe the interpolated coordinates will be available as tooltip vars $rx and $ry (I think this might have been neglected in the docs)

Edit: nevermind, rx and ry are interpolated screen coordinates, not useful here.

1 Like

I’ve gotten similar comments from users before about this (half the reason I fleshed the idea out above was out of my own self interest :joy:). They want a more ā€œtraditionalā€ consistent legend denoting everything being shown in the plot area, with the added optional enhancement of the values of these items being updated on hover. A bit of cake and eating it too… but a big reason for this (and I understand where they’re coming from), is that while the hovertools can obviate a lot of need for annotations like legends, they don’t eliminate it when a) the user wants to take an image screengrab and dump it in powerpoint for a conference presentation or something, or b) they require a pdf regulatory submission and again need fully annotated static images/figures.

@Bryan thanks for your question. I like to set up an automated system to display measurement data. There is the need to evaluate the relations of different subsystems (shown in linked plots) over time. So I like to move the crosshair left and right and at the same time look at values in different plots. Since the values change their position in the y-axis it is difficult to find the values quickly. With one big hoverbox i dont see the plot lines anymore. In the legend, the values for a specific measurement signal are always at the same position, so they are easy to find. Secondary it is required to plot 8+ lines in one plot and with the hover tool it just gets too clouded and overlapping.
If you have a better idea, I would love to hear it. Because I’m afraid that my JavaScript skills might not good enough to pull this off.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.