Dynamically change legend label with Bokeh 3.6

The following worked in 2.4.3

figure.legend.items[0].labe[‘value’] = label

However with 3.6.0 I get

TypeError: ‘Value’ object does not support item assignment

Changing to

figure.legend.items[0].label.value = label

Fixes the exception but it doesn’t make it work.

With 2.4.3 we’d dynamically change the highlighted text as the zoom level changed:

How can we make this work with 3.6.0?

I would expect just

figure.legend.items[0].label = new_value

to work.

I would have experimented and verified this myself, but you did not provide a complete Minimal Reproducible Example.

The support for Bokeh is incredibly frustrating. We are encountering so many problems that appear to be bugs. However your response is always “we’re not going to investigate the problem unless you supply an MRE”.

Our application is very large and complex, it’s not possible to just give you an MRE.

So we seem to be at an impasse where Bokeh has lots of bugs but you won’t even look at them.

we’re not going to investigate the problem unless you supply an MRE

What, exactly, are we supposed to investigate? I provided all the suggestion above that it is possible for me to give without actually running code. Did you try it?

Our application is very large and complex, it’s not possible to just give you an MRE.

Over the years, many users with large and complex applications have been able to supply MREs in order to help obtain better support. In such cases such as this, the onus is on you to create a “toy” MRE that is distilled down to just the code necessary to display the problem at hand.

Here is code which works with Bokeh 2.4.3:

from bokeh.layouts import column
from bokeh.models import Legend, LegendItem
from bokeh.plotting import figure, curdoc
from bokeh.sampledata.autompg import autompg
from bokeh.transform import jitter

years = sorted(autompg.yr.unique())

# Some example graphs from example on web
p1 = figure(width=600, height=300, title="Years vs mpg without jittering")
p1.xgrid.grid_line_color = None
p1.xaxis.ticker = years

p1.scatter(x='yr', y='mpg', size=9, alpha=0.4, source=autompg)

p2 = figure(width=600, height=300, title="Years vs mpg with jittering")
p2.xgrid.grid_line_color = None
p2.xaxis.ticker = years

scatter = p2.scatter(x=jitter('yr', 0.4), y='mpg', size=9, alpha=0.4, source=autompg)

# Add a legend
legend_items = []
legend_items.append(LegendItem(label='line X', renderers=[scatter]))
legend = Legend(items=legend_items)
p2.add_layout(legend,place='right')

# Add a label to the legend
p2.legend.items.insert(0, LegendItem(label="label", tags=['legend_label']))

# Add a callback which changes the label on the legend
def _on_x_range_end_changed(attr, old, new):
    print("change:", attr, old, new)
    p2.legend.items[0].label['value'] = f"{attr} {old}->{new}"

p1.x_range.on_change('end', _on_x_range_end_changed)

# add column - need to run this with bokeh --serve
curdoc().add_root(column(p1, p2))

However this doesn’t work with Bokeh 3.6.0. Get “TypeError: ‘Value’ object does not support item assignment”.

Would expect this to work:

    p2.legend.items[0].label.value = f"{attr} {old}->{new}"

but though I think the value is being updated the legend doesn’t update.

You need to set the actual property value itself, as I suggested originally, but because strings are ambiguous in this context (they can either be the label value itself, or the name of a CDS field to group from) you need to be explicit that you intend the string to be a value and not a field:

    from bokeh.core.properties import value
    p2.legend.items[0].label = value(f"{attr} {old}->{new}")

Sometimes Bokeh can auto-magically infer the (usually) right thing to do. Maybe this could or should be one of those times. Now that there is an MRE to focus discussions, feel free to open a GitHub development discussion if you’d like something to be considered further.

Regarding the ["value"] version AFAIK that is not anything that was ever demonstrated in our docs or examples. I think you just got “lucky” with some undefined and/or undocumented behavior.

Lastly, not to belabor the point, but this is why MREs are crucial, and required. I was able to answer this within five minutes of running actual code. There are hundred of thousands of users, a handful of us devs, and zero millions in VC funding. In true volunteer open-source contexts, users must meet devs in the middle, nothing else is sustainable. If you require a higher level of support than that, then you might want to consider commercial products with paid support offerings (that’s a sincere suggestion, not snark).