My problem is identical to the one discussed here:
I think I used the solution from above for my own plots. Now I am trying to upgrade from bokeh 2.x to bokeh 3.x and this does not work anymore. The original solution from the linked post also does not work in bokeh 3.8.1.
Here is my own example code to test both versions:
"""I often produce plots that show many lines. I need a way to first hide
all of them, so I can re-enable only those I actually want to look at.
The custom "hide" button allowed that in bokeh 2.x, but does not work
anymore in bokeh 3.8.
It still hides the lines, but the muting of the legend entries is not
synced anymore, which makes it kind of pointless.
Explicitly hiding the legend items makes the whole legend disappear,
which is not the desired effect.
Going click_policy="mute" + muted_alpha=0 gives the same result
as click_policy="hide"
"""
import numpy as np
from numpy import pi, sin, cos
import bokeh
from bokeh.layouts import row, column
from bokeh.models.widgets import Div
from bokeh.models import ColumnDataSource, Button, CustomJS
from bokeh.plotting import figure, show
from packaging.version import parse
x = np.linspace(-2*pi, 2*pi, 400)
y = sin(x)
y2 = cos(x)
source = ColumnDataSource(data=dict(x=x, y=y, y2=y2))
p = figure(title="Test", height=300,
sizing_mode='stretch_width',
)
r1 = p.line('x', 'y', source=source, legend_label="Sin", color='blue', muted_alpha=0)
r2 = p.line('x', 'y2', source=source, legend_label="Cos", color='red', muted_alpha=0)
p.yaxis.axis_label = 'y-axis Label [unit]'
p.legend.location = "top_left"
# p.legend.click_policy = "hide" # clickable legend items
p.legend.click_policy = "mute" # clickable legend items
renderers = [r1, r2]
# Create a button to hide/show all lines
button = Button(label="Hide", button_type="default", height=50, width=50,
sizing_mode='fixed')
# Working callback for Bokeh 2.x
callback_2 = CustomJS(args=dict(lines=renderers, button=button),
code="""
let isVisible = button.label == 'Hide'
for (let i = 0; i < lines.length; i++) {
lines[i].visible = !isVisible;
}
button.label = isVisible ? 'Show' : 'Hide';
""")
# Broken callback for Bokeh 3.x
callback_3 = CustomJS(args=dict(lines=renderers, button=button,
items=p.legend[0].items),
code="""
let isVisible = button.label == 'Hide'
for (let i = 0; i < lines.length; i++) {
lines[i].visible = !isVisible;
}
// Hiding the legend entries manually makes it disappear completely
for (let i = 0; i < items.length; i++) {
items[i].visible = !isVisible;
}
button.label = isVisible ? 'Show' : 'Hide';
""")
if parse(bokeh.__version__) < parse("3.0"):
button.js_on_click(callback_2)
else:
button.js_on_click(callback_3)
text = Div(
text="The button should hide the lines, while only muting their "
"legend items (as if all legend items were clicked). "
f"Bokeh: {bokeh.__version__}",
height=15, sizing_mode='stretch_width')
page_row = column([text,
row([button, p], sizing_mode='stretch_width')],
sizing_mode='stretch_width'
)
show(page_row)
