I have implemented a Bokeh figure with
circle()
with a legend_group
for colouring and for creating an interactive legend.I have set click_policy=hide
for the legend so that points (circles) from a specific group are hidden when I click on a legend item.
All of that works fine initially. When I move the slider, however, the legend "breaks":
This is what the initial plot looks like:
When I move the slider (at the bottom of the plot), some groups are correctly filtered out from the plot, but only one legend item remains, while multiple groups are still visible in the plot.
The code is embedded into an object-oriented implementation. This is the method that adds the circles to the figure:
def _add_circles(self):
palette = self._select_palette()
labels = self._data[self._LABEL_FIELD].unique().tolist()
for cluster in self._clusters:
glyph = self._figure.circle(
source=self._source,
x="x",
y="y",
color=factor_cmap(self._LABEL_FIELD, palette, labels),
legend_group=self._LABEL_FIELD,
view=CDSView(
filter=GroupFilter(
column_name=self._LABEL_FIELD, group=cluster.label
),
),
)
if cluster.label == OUTLIERS_LABEL:
glyph.visible = False
This method sets up the legend:
def _setup_legend(self, legend_location: str = "right", click_policy: str = "hide"):
legend = self._figure.legend[0]
legend.label_text_font_size = "6px"
legend.spacing = 0
legend.location = legend_location
legend.click_policy = click_policy
The slider with callback is added like this:
def _year_slider(self) -> RangeSlider:
def callback(attr, old, new): # noqa: unused-argument
self._source.data = self._data.loc[
self._data.year.between(new[0], new[1])
].to_dict(orient="list")
min_year: int = self._data[self._YEAR_COLUMN].min()
max_year: int = self._data[self._YEAR_COLUMN].max()
slider = RangeSlider(
start=min_year,
end=max_year,
value=(min_year, max_year),
width=self._figure.frame_width,
)
slider.on_change("value_throttled", callback)
return slider
When I move the slider back to its original span, all legend items are displayed correctly again.
The Legend
object still contains all the LegendItem
objects after moving the slider. All of them still have the visible
property set to true
.
The question is: why the legend does not display all its items? How is this related to the slider and/or the callback the slider uses?
It looks like using legend_label=cluster.label
(where cluster.label
is a string literal) instead of legend_group="year"
fixes the issue.
This is indicated in the documentation about interactive legends:
The features of interactive legends currently only work on the basic legend labels described above. Legends that are created by specifying a column to automatically group do not yet support interactive features.