5

Is there any way to interactively change legend label text in Bokeh?

I've read https://github.com/bokeh/bokeh/issues/2274 and How to interactively display and hide lines in a Bokeh plot? but neither are applicable.

I don't need to modify the colors or anything of more complexity than changing the label text but I can't find a way to do it.

Community
  • 1
  • 1
Eric Kaschalk
  • 369
  • 1
  • 4
  • 8

4 Answers4

4

I hope this answer can help others with similar issues.

There is a workaround to this problem: starting from version 0.12.3 your legends can be dynamically modified through a ColumnDataSource object used to generate the given elements. For example:

source_points = ColumnDataSource(dict(
                x=[1, 2, 3, 4, 5, 6],
                y=[2, 1, 2, 1, 2, 1],
                color=['blue','red','blue','red','blue','red'],
                category=['hi', 'lo', 'hi', 'lo', 'hi', 'lo']
                ))
self._figure.circle('x',
                    'y',
                    color='color',
                    legend='category',
                    source=source_points)

Then you should be able to update the legend by setting the category values again, like:

# must have the same length
source_points.data['category'] = ['stack', 'flow', 'stack', 'flow', 'stack', 'flow']

Note the relation between category and color. If you had something like this:

source = ColumnDataSource(dict(
        x=[1, 2, 3, 4, 5, 6],
        y=[2, 1, 2, 1, 2, 1],
        color=['blue','red','blue','red','blue','red'],
        category=['hi', 'hi', 'hi', 'lo', 'hi', 'lo']
    ))

Then the second hi would show up blue as well. It only matches the first occurrence.

Gabriel
  • 194
  • 1
  • 8
  • If anyone is wondering, now instead of legend="category" (deprecated) you have to use legend_field="category" (or whichever field you have used in the dictionary). – Guillermo J. Feb 10 '21 at 10:38
1

As of Bokeh 0.12.1 it does not look like this is currently supported. Legend objects have a legends property that maps the text to a list of glyphs:

{
    "foo": [circle1], 
    "bar": [line2, circle2]
}

Ideally, you could update this legends property to cause it to re-render. But looking at the source code it appears the value is used at initialization, but there is no plumbing to force a re-render if the value changes. A possible workaround could be to change the value of legends then also immediately set some other property that does trigger a re-render.

In any case making this work on update should not be much work, and would be a nice PR for a new contributor. I'd encourage you to submit a feature request issue on the GitHub issue tracker and, if you have the ability a Pull Request to implement it (we are always happy to help new contributors get started and answer questions)

bigreddot
  • 33,642
  • 5
  • 69
  • 122
1

In my case, I made it work with the next code:

from bokeh.plotting import figure, show

# Create and show the plot
plt = figure()
handle = show(plt, notebook_handle=True)

# Update the legends without generating the whole plot once shown
for legend in plt.legend:
    for legend_item, new_value in zip(legend.items, new_legend_values):
        legend_item.label['value'] = new_value
push_notebook(handle=handle)

In my case, I was plotting some distributions, and updating then interactively (like an animation of the changes in the distributions). In the legend, I have the parameters of the distribution over time, which I need to update at each iteration, as they change.

Note that this code only works in a Jupyter notebook.

Marc Garcia
  • 3,287
  • 2
  • 28
  • 37
  • Would you please give a more complete example, I'm getting "ValueError: PATCH-DOC message requires at least one event" error all the time. – Antony Hatchkins Nov 22 '17 at 15:28
  • I guess this is caused by a different version of Bokeh. You should check in the traceback which line is failing, and in the documentation of that the failing function for your version, what's expecting. If you don't find the solution, post a new question in stackoverlow, and feel free to add a link to this answer. – Marc Garcia Nov 30 '17 at 09:56
0

I ended up just redrawing the entire graph each times since the number of lines also varied in my case.

A small working Jupyter notebook example:

from bokeh.io import show
from bokeh.plotting import figure
from bokeh.palettes import brewer
from math import sin, pi
output_notebook()   

def update(Sine):
    p = figure() 
    r = []
    for i in range(sines.index(Sine) + 1):
        y = [sin(xi/(10*(i+1))) for xi in x]
        r.append(p.line(x, y, legend=labels[i], color=colors[i], line_width = 3))

    show(p, notebook_handle=True)
    push_notebook()  

sines = ["one sine", "two sines", "three sines"]
labels = ["First sine", "second sine", "Third sine"]
colors = brewer['BuPu'][3]
x = [i for i in range(100)]

interact(update, Sine=sines)

enter image description here

Roald
  • 2,459
  • 16
  • 43