Repository: holoviz/holoviews
Pull request #5911 (2023-09-28 17:08:26)

Small improvements to vectorized annotators
While working on implementing vectorized annotators in Holonote, I noticed some edge cases. Which was not accounted for in the original implementation. (and some extra Pandas 2.1 warnings, e.g.  `next(iter(...))`)

- [x] Add tests


Patch:
diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py
index 81fd6f075..ece8d2609 100644
--- a/holoviews/ipython/__init__.py
+++ b/holoviews/ipython/__init__.py
@@ -186,7 +186,7 @@ class notebook_extension(extension):
         same_cell_execution = getattr(self, '_repeat_execution_in_cell', False)
         for r in [r for r in resources if r != 'holoviews']:
             Store.renderers[r].load_nb(inline=p.inline)
-        Renderer.load_nb(inline=p.inline, reloading=same_cell_execution, enable_mathjax=self.enable_mathjax)
+        Renderer.load_nb(inline=p.inline, reloading=same_cell_execution, enable_mathjax=p.enable_mathjax)
 
         if hasattr(ip, 'kernel') and not loaded:
             Renderer.comm_manager.get_client_comm(notebook_extension._process_comm_msg,
diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py
index 201d397d2..1bef23a4c 100644
--- a/holoviews/plotting/bokeh/annotation.py
+++ b/holoviews/plotting/bokeh/annotation.py
@@ -1,3 +1,4 @@
+import itertools
 from collections import defaultdict
 from html import escape
 
@@ -51,7 +52,11 @@ class _SyntheticAnnotationPlot(ColorbarPlot):
 
     def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
         figure = super().initialize_plot(ranges=ranges, plot=plot, plots=plots, source=source)
-        labels = "yx" if self.invert_axes else "xy"
+        # Only force labels if no other ranges are set
+        if self.overlaid and set(itertools.chain.from_iterable(ranges)) - {"HSpans", "VSpans", "VLines", "HLines"}:
+            return figure
+        labels = [self.xlabel or "x", self.ylabel or "y"]
+        labels = labels[::-1] if self.invert_axes else labels
         for ax, label in zip(figure.axis, labels):
             ax.axis_label = label
         return figure
diff --git a/holoviews/util/__init__.py b/holoviews/util/__init__.py
index e63bf3f48..6386bd68a 100644
--- a/holoviews/util/__init__.py
+++ b/holoviews/util/__init__.py
@@ -715,6 +715,9 @@ class extension(_pyviz_extension):
 
         import panel as pn
 
+        if params.get("enable_mathjax", False) and selected_backend == "bokeh":
+            pn.extension("mathjax")
+
         if pn.config.comms == "default":
             if "google.colab" in sys.modules:
                 pn.config.comms = "colab"


Test patch:
diff --git a/holoviews/tests/plotting/bokeh/test_annotationplot.py b/holoviews/tests/plotting/bokeh/test_annotationplot.py
index b9da8a113..3525e80a1 100644
--- a/holoviews/tests/plotting/bokeh/test_annotationplot.py
+++ b/holoviews/tests/plotting/bokeh/test_annotationplot.py
@@ -232,6 +232,15 @@ class TestHVLinesPlot(TestBokehPlot):
         assert (source.data["y"] == [0, 1, 2, 5.5]).all()
         assert (source.data["extra"] == [-1, -2, -3, -44]).all()
 
+    def test_hlines_xlabel_ylabel(self):
+        hlines = HLines(
+            {"y": [0, 1, 2, 5.5], "extra": [-1, -2, -3, -44]}, vdims=["extra"]
+        ).opts(xlabel="xlabel", ylabel="xlabel")
+        plot = bokeh_renderer.get_plot(hlines)
+        assert isinstance(plot.handles["glyph"], BkHSpan)
+        assert plot.handles["xaxis"].axis_label == "xlabel"
+        assert plot.handles["yaxis"].axis_label == "xlabel"
+
     def test_hlines_array(self):
         hlines = HLines(np.array([0, 1, 2, 5.5]))
         plot = bokeh_renderer.get_plot(hlines)
@@ -357,6 +366,18 @@ class TestHVLinesPlot(TestBokehPlot):
         assert plot.handles["y_range"].start == 0
         assert plot.handles["y_range"].end == 5.5
 
+    def test_vlines_hlines_overlay_non_annotation(self):
+        non_annotation = hv.Curve([], kdims=["time"])
+        hlines = HLines(
+            {"y": [0, 1, 2, 5.5], "extra": [-1, -2, -3, -44]}, vdims=["extra"]
+        )
+        vlines = VLines(
+            {"x": [0, 1, 2, 5.5], "extra": [-1, -2, -3, -44]}, vdims=["extra"]
+        )
+        plot = bokeh_renderer.get_plot(non_annotation * hlines * vlines)
+        assert plot.handles["xaxis"].axis_label == "time"
+        assert plot.handles["yaxis"].axis_label == "y"
+
     def test_coloring_hline(self):
         hlines = HLines({"y": [1, 2, 3]})
         hlines = hlines.opts(
@@ -400,6 +421,15 @@ class TestHVSpansPlot(TestBokehPlot):
         assert (source.data["y1"] == [1, 4, 6.5]).all()
         assert (source.data["extra"] == [-1, -2, -3]).all()
 
+    def test_hspans_plot_xlabel_ylabel(self):
+        hspans = HSpans(
+            {"y0": [0, 3, 5.5], "y1": [1, 4, 6.5], "extra": [-1, -2, -3]}, vdims=["extra"]
+        ).opts(xlabel="xlabel", ylabel="xlabel")
+        plot = bokeh_renderer.get_plot(hspans)
+        assert isinstance(plot.handles["glyph"], BkHStrip)
+        assert plot.handles["xaxis"].axis_label == "xlabel"
+        assert plot.handles["yaxis"].axis_label == "xlabel"
+
     def test_hspans_plot_invert_axes(self):
         hspans = HSpans(
             {"y0": [0, 3, 5.5], "y1": [1, 4, 6.5], "extra": [-1, -2, -3]}, vdims=["extra"]
@@ -514,6 +544,18 @@ class TestHVSpansPlot(TestBokehPlot):
         assert plot.handles["y_range"].start == 0
         assert plot.handles["y_range"].end == 6.5
 
+    def test_vlines_hlines_overlay_non_annotation(self):
+        non_annotation = hv.Curve([], kdims=["time"])
+        hspans = HSpans(
+            {"y0": [0, 3, 5.5], "y1": [1, 4, 6.5], "extra": [-1, -2, -3]}, vdims=["extra"]
+        )
+        vspans = VSpans(
+            {"x0": [0, 3, 5.5], "x1": [1, 4, 6.5], "extra": [-1, -2, -3]}, vdims=["extra"]
+        )
+        plot = bokeh_renderer.get_plot(non_annotation * hspans * vspans)
+        assert plot.handles["xaxis"].axis_label == "time"
+        assert plot.handles["yaxis"].axis_label == "y"
+
     def test_coloring_hline(self):
         hspans = HSpans({"y0": [1, 3, 5], "y1": [2, 4, 6]}).opts(
             alpha=hv.dim("y0").norm(),