Repository: holoviz/holoviews

Issue:
Document `sync_legends` and `select_legends`
https://github.com/holoviz/holoviews/pull/5763 added the`select_legends` and `sync_legends` utility, and the `sync_legends` option to grid plots. It'd be nice if these were documented in https://holoviews.org/user_guide/Plotting_with_Bokeh.html.


Discussion:
It would also be nicer if `select_legends` were a method on `Layout` and `GridSpace` or at least a top-level function.

This import seems wrong : `from holoviews.plotting.bokeh.util import select_legends`

Resolving patch:
diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py
--- a/holoviews/plotting/bokeh/plot.py
+++ b/holoviews/plotting/bokeh/plot.py
@@ -31,7 +31,7 @@
 from .links import LinkCallback
 from .util import (
     bokeh3, filter_toolboxes, make_axis, sync_legends, update_shared_sources, empty_plot,
-    decode_bytes, theme_attr_json, cds_column_replace, get_default, merge_tools,
+    decode_bytes, theme_attr_json, cds_column_replace, get_default, merge_tools, select_legends
 )
 
 if bokeh3:
@@ -690,6 +690,21 @@ class LayoutPlot(CompositePlot, GenericLayoutPlot):
     sync_legends = param.Boolean(default=True, doc="""
         Whether to sync the legend when muted/unmuted based on the name""")
 
+    show_legends = param.ClassSelector(default=None, class_=(list, int, bool), doc="""
+        Whether to show the legend for a particular subplot by index. If True all legends
+        will be shown. If False no legends will be shown.""")
+
+    legend_position = param.ObjectSelector(objects=["top_right",
+                                                    "top_left",
+                                                    "bottom_left",
+                                                    "bottom_right",
+                                                    'right', 'left',
+                                                    'top', 'bottom'],
+                                                    default="top_right",
+                                                    doc="""
+        Allows selecting between a number of predefined legend position
+        options. Will only be applied if show_legend is not None.""")
+
     tabs = param.Boolean(default=False, doc="""
         Whether to display overlaid plots in separate panes""")
 
@@ -700,6 +715,11 @@ def __init__(self, layout, keys=None, **params):
             self.traverse(lambda x: attach_streams(self, x.hmap, 2),
                           [GenericElementPlot])
 
+    @param.depends('show_legends', 'legend_position', watch=True, on_init=True)
+    def _update_show_legend(self):
+        if self.show_legends is not None:
+            select_legends(self.layout, self.show_legends, self.legend_position)
+
     def _init_layout(self, layout):
         # Situate all the Layouts in the grid and compute the gridspec
         # indices for all the axes required by each LayoutPlot.
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -33,7 +33,7 @@
 
 from ...core.layout import Layout
 from ...core.ndmapping import NdMapping
-from ...core.overlay import Overlay
+from ...core.overlay import Overlay, NdOverlay
 from ...core.util import (
     arraylike_types, callable_name, cftime_types,
     cftime_to_timestamp, isnumeric, pd, unique_array
@@ -468,20 +468,25 @@ def select_legends(holoviews_layout, figure_index=None, legend_position="top_rig
     ----------
     holoviews_layout : Holoviews Layout
         Holoviews Layout with legends.
-    figure_index : list[int] | int | None
+    figure_index : list[int] | bool | int | None
         Index of the figures which legends to show.
         If None is chosen, only the first figures legend is shown
+        If True is chosen, all legends are shown.
     legend_position : str
         Position of the legend(s).
     """
     if figure_index is None:
         figure_index = [0]
+    elif isinstance(figure_index, bool):
+        figure_index = range(len(holoviews_layout)) if figure_index else []
     elif isinstance(figure_index, int):
         figure_index = [figure_index]
     if not isinstance(holoviews_layout, Layout):
         holoviews_layout = [holoviews_layout]
 
     for i, plot in enumerate(holoviews_layout):
+        if not isinstance(plot, (NdOverlay, Overlay)):
+            continue
         if i in figure_index:
             plot.opts(show_legend=True, legend_position=legend_position)
         else:


Test patch:
diff --git a/holoviews/tests/plotting/bokeh/test_utils.py b/holoviews/tests/plotting/bokeh/test_utils.py
--- a/holoviews/tests/plotting/bokeh/test_utils.py
+++ b/holoviews/tests/plotting/bokeh/test_utils.py
@@ -1,6 +1,9 @@
+import pytest
+
+import holoviews as hv
 from holoviews.core import Store
 from holoviews.element.comparison import ComparisonTestCase
-from holoviews.plotting.bokeh.util import filter_batched_data, glyph_order
+from holoviews.plotting.bokeh.util import filter_batched_data, glyph_order, select_legends
 from holoviews.plotting.bokeh.styles import expand_batched_style
 
 bokeh_renderer = Store.renderers['bokeh']
@@ -74,3 +77,28 @@ def test_glyph_order(self):
         order = glyph_order(['scatter_1', 'patch_1', 'rect_1'],
                             ['scatter', 'patch'])
         self.assertEqual(order, ['scatter_1', 'patch_1', 'rect_1'])
+
+
+@pytest.mark.parametrize(
+    "figure_index,expected",
+    [
+        (0, [True, False]),
+        (1, [False, True]),
+        ([0], [True, False]),
+        ([1], [False, True]),
+        ([0, 1], [True, True]),
+        (True, [True, True]),
+        (False, [False, False]),
+        (None, [True, False]),
+    ],
+    ids=["int0", "int1", "list0", "list1", "list01", "True", "False", "None"],
+)
+def test_select_legends_figure_index(figure_index, expected):
+    overlays = [
+        hv.Curve([0, 0]) * hv.Curve([1, 1]),
+        hv.Curve([2, 2]) * hv.Curve([3, 3]),
+    ]
+    layout = hv.Layout(overlays)
+    select_legends(layout, figure_index)
+    output = [ol.opts["show_legend"] for ol in overlays]
+    assert expected == output