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.