Repository: holoviz/holoviews

Issue:
Popup is clipped / hidden behind plot
Not sure if it's possible to adjust the zorder of the Bokeh popup panel?

```
import numpy as np
import panel as pn
import holoviews as hv


points = hv.Points(np.random.randn(1000, 2))

def form(name):
    text_input = pn.widgets.TextInput(name='Description')
    button = pn.widgets.Button(name='Save', on_click=lambda _: layout.param.update(visible=False))
    layout = pn.Column(f'# {name}', text_input, button)
    return layout

hv.streams.BoundsXY(source=points, popup=form('Bounds'))
hv.streams.Lasso(source=points, popup=form('Lasso'))
hv.streams.Tap(source=points, popup=form('Tap'))

points.opts(tools=['box_select', 'lasso_select', 'tap'], width=600, height=600, size=6, color='black', fill_color=None) + points.clone()
```
<img width="1092" alt="image" src="https://github.com/holoviz/holoviews/assets/15331990/7b9d9d2e-cc49-4836-86c5-5fb87f59c70f">


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/holoviz/holoviews/pull/6297?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.52%. Comparing base [(`febe075`)](https://app.codecov.io/gh/holoviz/holoviews/commit/febe07506bda2c433ac31938ce4c5aa62335559d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) to head [(`e07ec68`)](https://app.codecov.io/gh/holoviz/holoviews/commit/e07ec682521b7431c5fe96f7aceeee021ed0e938?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6297      +/-   ##
==========================================
- Coverage   88.52%   88.52%   -0.01%     
==========================================
  Files         323      323              
  Lines       67996    67996              
==========================================
- Hits        60193    60191       -2     
- Misses       7803     7805       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/holoviz/holoviews/pull/6297?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).


Patch:
diff --git a/holoviews/core/data/pandas.py b/holoviews/core/data/pandas.py
index a097bdf95..a667fd92d 100644
--- a/holoviews/core/data/pandas.py
+++ b/holoviews/core/data/pandas.py
@@ -72,7 +72,7 @@ class PandasInterface(Interface, PandasAPI):
 
             if kdims and not (len(kdims) == len(index_names) and {dimension_name(kd) for kd in kdims} == set(index_names)):
                 kdim = dimension_name(kdims[0])
-                if eltype._auto_indexable_1d and ncols == 1 and kdim not in data.columns:
+                if eltype._auto_indexable_1d and ncols == 1 and kdim not in [*data.columns, *cls.indexes(data)]:
                     data = data.copy()
                     data.insert(0, kdim, np.arange(len(data)))
 
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
index 82698dbf7..a80bf1e4e 100644
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -613,7 +613,8 @@ class PopupMixin:
             position=XY(x=np.nan, y=np.nan),
             anchor="top_left",
             elements=[close_button],
-            visible=False
+            visible=False,
+            styles={"zIndex": "1000"},
         )
         close_button.js_on_click(CustomJS(args=dict(panel=self._panel), code="panel.visible = false"))
 
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 0bbd705ad..17ef69983 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -922,7 +922,8 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
             is_dt = isdatetime(xvals)
             if is_dt or xvals.dtype.kind not in 'OU':
                 xdiff = np.abs(np.diff(xvals))
-                if len(np.unique(xdiff)) == 1 and xdiff[0] == 0:
+                diff_size = len(np.unique(xdiff))
+                if diff_size == 0 or (diff_size == 1 and xdiff[0] == 0):
                     xdiff = 1
                 if is_dt:
                     width *= xdiff.astype('timedelta64[ms]').astype(np.int64)
diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py
index 879fd260e..5da620b85 100644
--- a/holoviews/plotting/mpl/chart.py
+++ b/holoviews/plotting/mpl/chart.py
@@ -1,7 +1,6 @@
 import matplotlib as mpl
 import numpy as np
 import param
-from matplotlib import cm
 from matplotlib.collections import LineCollection
 from matplotlib.dates import DateFormatter, date2num
 from packaging.version import Version
@@ -27,7 +26,7 @@ from ..util import compute_sizes, dim_range_key, get_min_distance, get_sideplot_
 from .element import ColorbarPlot, ElementPlot, LegendPlot
 from .path import PathPlot
 from .plot import AdjoinedPlot, mpl_rc_context
-from .util import mpl_version
+from .util import MPL_GE_3_7, MPL_GE_3_9, mpl_version
 
 
 class ChartPlot(ElementPlot):
@@ -95,7 +94,10 @@ class CurvePlot(ChartPlot):
     def init_artists(self, ax, plot_args, plot_kwargs):
         xs, ys = plot_args
         if isdatetime(xs):
-            artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0]
+            if MPL_GE_3_9:
+                artist = ax.plot(xs, ys, '-', **plot_kwargs)[0]
+            else:
+                artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0]
         else:
             artist = ax.plot(xs, ys, **plot_kwargs)[0]
         return {'artist': artist}
@@ -501,7 +503,12 @@ class SideHistogramPlot(AdjoinedPlot, HistogramPlot):
         # Get colormapping options
         if isinstance(range_item, (HeatMap, Raster)) or (cdim and cdim in element):
             style = self.lookup_options(range_item, 'style')[self.cyclic_index]
-            cmap = cm.get_cmap(style.get('cmap'))
+            if MPL_GE_3_7:
+                # https://github.com/matplotlib/matplotlib/pull/28355
+                cmap = mpl.colormaps.get_cmap(style.get('cmap'))
+            else:
+                from matplotlib import cm
+                cmap = cm.get_cmap(style.get('cmap'))
             main_range = style.get('clims', main_range)
         else:
             cmap = None
diff --git a/holoviews/plotting/mpl/stats.py b/holoviews/plotting/mpl/stats.py
index f728404c3..9764de4e1 100644
--- a/holoviews/plotting/mpl/stats.py
+++ b/holoviews/plotting/mpl/stats.py
@@ -6,6 +6,7 @@ from ..mixins import MultiDistributionMixin
 from .chart import AreaPlot, ChartPlot
 from .path import PolygonPlot
 from .plot import AdjoinedPlot
+from .util import MPL_GE_3_9
 
 
 class DistributionPlot(AreaPlot):
@@ -77,7 +78,10 @@ class BoxPlot(MultiDistributionMixin, ChartPlot):
             d = group[group.vdims[0]]
             data.append(d[np.isfinite(d)])
             labels.append(label)
-        style['labels'] = labels
+        if MPL_GE_3_9:
+            style['tick_labels'] = labels
+        else:
+            style['labels'] = labels
         style = {k: v for k, v in style.items()
                  if k not in ['zorder', 'label']}
         style['vert'] = not self.invert_axes
@@ -157,7 +161,10 @@ class ViolinPlot(BoxPlot):
         stats_color = plot_kwargs.pop('stats_color', 'black')
         facecolors = plot_kwargs.pop('facecolors', [])
         edgecolors = plot_kwargs.pop('edgecolors', 'black')
-        labels = plot_kwargs.pop('labels')
+        if MPL_GE_3_9:
+            labels = {'tick_labels': plot_kwargs.pop('tick_labels')}
+        else:
+            labels = {'labels': plot_kwargs.pop('labels')}
         alpha = plot_kwargs.pop('alpha', 1.)
         showmedians = self.inner == 'medians'
         bw_method = self.bandwidth or 'scott'
@@ -168,7 +175,7 @@ class ViolinPlot(BoxPlot):
                              showfliers=False, showcaps=False, patch_artist=True,
                              boxprops={'facecolor': box_color},
                              medianprops={'color': 'white'}, widths=0.1,
-                             labels=labels)
+                             **labels)
             artists.update(box)
         for body, color in zip(artists['bodies'], facecolors):
             body.set_facecolors(color)
@@ -199,7 +206,10 @@ class ViolinPlot(BoxPlot):
             labels.append(label)
             colors.append(elstyle[i].get('facecolors', 'blue'))
         style['positions'] = list(range(len(data)))
-        style['labels'] = labels
+        if MPL_GE_3_9:
+            style['tick_labels'] = labels
+        else:
+            style['labels'] = labels
         style['facecolors'] = colors
 
         if element.ndims > 0:
diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py
index 4699d8590..99c5a9b37 100644
--- a/holoviews/plotting/mpl/util.py
+++ b/holoviews/plotting/mpl/util.py
@@ -37,6 +37,8 @@ from ...element import RGB, Polygons, Raster
 from ..util import COLOR_ALIASES, RGB_HEX_REGEX
 
 mpl_version = Version(mpl.__version__)
+MPL_GE_3_7 = mpl_version >= Version('3.7')
+MPL_GE_3_9 = mpl_version >= Version('3.9')
 
 
 def is_color(color):
diff --git a/pyproject.toml b/pyproject.toml
index 4ff028092..a95d6e4d5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -119,6 +119,8 @@ filterwarnings = [
     # 2024-06
     "ignore:\\s*Dask dataframe query planning is disabled because dask-expr is not installed:FutureWarning", # OK
     "ignore:unclosed file <_io.TextIOWrapper name='/dev/null' mode='w':ResourceWarning", # OK
+    # 2024-07
+    "ignore:The (non_)?interactive_bk attribute was deprecated in Matplotlib 3.9", # OK - Only happening in debug mode
 ]
 
 [tool.coverage]


Test patch:
diff --git a/holoviews/tests/core/data/test_pandasinterface.py b/holoviews/tests/core/data/test_pandasinterface.py
index bb2e520d7..d40eefc93 100644
--- a/holoviews/tests/core/data/test_pandasinterface.py
+++ b/holoviews/tests/core/data/test_pandasinterface.py
@@ -391,3 +391,9 @@ class PandasInterfaceMultiIndex(HeterogeneousColumnTests, InterfaceTests):
         assert list(grouped.keys()) == [0, 1, 2, 3]
         for k, v in grouped.items():
             pd.testing.assert_frame_equal(v.data, ds.select(values=k).data)
+
+    def test_regression_no_auto_index(self):
+        # https://github.com/holoviz/holoviews/issues/6298
+
+        plot = Scatter(self.df, kdims="number")
+        np.testing.assert_equal(plot.dimension_values('number'), self.df.index.get_level_values('number'))
diff --git a/holoviews/tests/plotting/bokeh/test_barplot.py b/holoviews/tests/plotting/bokeh/test_barplot.py
index fd16dbd7b..46345442f 100644
--- a/holoviews/tests/plotting/bokeh/test_barplot.py
+++ b/holoviews/tests/plotting/bokeh/test_barplot.py
@@ -37,6 +37,14 @@ class TestBarPlot(TestBokehPlot):
         for v in source.data.values():
             self.assertEqual(len(v), 0)
 
+    def test_bars_single_value(self):
+        df = pd.DataFrame({"time": [1], "value": [-1]})
+        bars = Bars(df)
+        plot = bokeh_renderer.get_plot(bars)
+        source = plot.handles['source']
+        assert source.data['time'], np.array([1])
+        assert source.data['value'], np.array([-1])
+
     def test_bars_grouped_categories(self):
         bars = Bars([('A', 0, 1), ('A', 1, -1), ('B', 0, 2)],
                     kdims=['Index', 'Category'], vdims=['Value'])
diff --git a/holoviews/tests/plotting/matplotlib/test_annotationplot.py b/holoviews/tests/plotting/matplotlib/test_annotationplot.py
index d1c188449..acd795994 100644
--- a/holoviews/tests/plotting/matplotlib/test_annotationplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_annotationplot.py
@@ -2,6 +2,7 @@ import numpy as np
 
 import holoviews as hv
 from holoviews.element import HLines, HSpans, VLines, VSpans
+from holoviews.plotting.mpl.util import MPL_GE_3_9
 
 from .test_plot import TestMPLPlot, mpl_renderer
 
@@ -153,6 +154,25 @@ class TestHVLinesPlot(TestMPLPlot):
 
 
 class TestHVSpansPlot(TestMPLPlot):
+
+    def _hspans_check(self, source, v0, v1):
+        # Matplotlib 3.9+ uses a rectangle instead of polygon
+        if MPL_GE_3_9:
+            rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()]
+            assert np.allclose(rect, [0, v0, 1, v1 - v0])
+        else:
+            assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0])
+            assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0])
+
+    def _vspans_check(self, source, v0, v1):
+        # Matplotlib 3.9+ uses a rectangle instead of polygon
+        if MPL_GE_3_9:
+            rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()]
+            assert np.allclose(rect, [v0, 0, v1 - v0, 1])
+        else:
+            assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0])
+            assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0])
+
     def test_hspans_plot(self):
         hspans = HSpans(
             {"y0": [0, 3, 5.5], "y1": [1, 4, 6.5], "extra": [-1, -2, -3]},
@@ -164,14 +184,17 @@ class TestHVSpansPlot(TestMPLPlot):
 
         xlim = plot.handles["fig"].axes[0].get_xlim()
         ylim = plot.handles["fig"].axes[0].get_ylim()
-        assert np.allclose(xlim, (-0.055, 0.055))
+        if MPL_GE_3_9:
+            assert np.allclose(xlim, (-0.05, 1.05))
+        else:
+            assert np.allclose(xlim, (-0.055, 0.055))
+
         assert np.allclose(ylim, (0, 6.5))
 
         sources = plot.handles["annotations"]
         assert len(sources) == 3
         for source, v0, v1 in zip(sources, hspans.data["y0"], hspans.data["y1"]):
-            assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0])
-            assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0])
+            self._hspans_check(source, v0, v1)
 
     def test_hspans_inverse_plot(self):
         hspans = HSpans(
@@ -190,8 +213,7 @@ class TestHVSpansPlot(TestMPLPlot):
         sources = plot.handles["annotations"]
         assert len(sources) == 3
         for source, v0, v1 in zip(sources, hspans.data["y0"], hspans.data["y1"]):
-            assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0])
-            assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0])
+            self._vspans_check(source, v0, v1)
 
     def test_dynamicmap_overlay_hspans(self):
         el = HSpans(data=[[1, 3], [2, 4]])
@@ -218,7 +240,10 @@ class TestHVSpansPlot(TestMPLPlot):
 
         xlim = plot.handles["fig"].axes[0].get_xlim()
         ylim = plot.handles["fig"].axes[0].get_ylim()
-        assert np.allclose(xlim, (-0.055, 0.055))
+        if MPL_GE_3_9:
+            assert np.allclose(xlim, (-0.05, 1.05))
+        else:
+            assert np.allclose(xlim, (-0.055, 0.055))
         assert np.allclose(ylim, (0, 6.5))
 
         sources = plot.handles["annotations"]
@@ -226,8 +251,7 @@ class TestHVSpansPlot(TestMPLPlot):
         for source, v0, v1 in zip(
             sources, hspans.data["other0"], hspans.data["other1"]
         ):
-            assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0])
-            assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0])
+            self._hspans_check(source, v0, v1)
 
     def test_vspans_plot(self):
         vspans = VSpans(
@@ -246,8 +270,7 @@ class TestHVSpansPlot(TestMPLPlot):
         sources = plot.handles["annotations"]
         assert len(sources) == 3
         for source, v0, v1 in zip(sources, vspans.data["x0"], vspans.data["x1"]):
-            assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0])
-            assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0])
+            self._vspans_check(source, v0, v1)
 
     def test_vspans_inverse_plot(self):
         vspans = VSpans(
@@ -260,14 +283,16 @@ class TestHVSpansPlot(TestMPLPlot):
 
         xlim = plot.handles["fig"].axes[0].get_xlim()
         ylim = plot.handles["fig"].axes[0].get_ylim()
-        assert np.allclose(xlim, (-0.055, 0.055))
+        if MPL_GE_3_9:
+            assert np.allclose(xlim, (-0.05, 1.05))
+        else:
+            assert np.allclose(xlim, (-0.055, 0.055))
         assert np.allclose(ylim, (0, 6.5))
 
         sources = plot.handles["annotations"]
         assert len(sources) == 3
         for source, v0, v1 in zip(sources, vspans.data["x0"], vspans.data["x1"]):
-            assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0])
-            assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0])
+            self._hspans_check(source, v0, v1)
 
     def test_vspans_nondefault_kdims(self):
         vspans = VSpans(
@@ -287,8 +312,7 @@ class TestHVSpansPlot(TestMPLPlot):
         for source, v0, v1 in zip(
             sources, vspans.data["other0"], vspans.data["other1"]
         ):
-            assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0])
-            assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0])
+            self._vspans_check(source, v0, v1)
 
     def test_vspans_hspans_overlay(self):
         hspans = HSpans(
@@ -310,12 +334,10 @@ class TestHVSpansPlot(TestMPLPlot):
 
         sources = plot.handles["fig"].axes[0].get_children()
         for source, v0, v1 in zip(sources[:3], hspans.data["y0"], hspans.data["y1"]):
-            assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0])
-            assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0])
+            self._hspans_check(source, v0, v1)
 
         for source, v0, v1 in zip(sources[3:6], vspans.data["x0"], vspans.data["x1"]):
-            assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0])
-            assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0])
+            self._vspans_check(source, v0, v1)
 
     def test_dynamicmap_overlay_vspans(self):
         el = VSpans(data=[[1, 3], [2, 4]])
diff --git a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py
index cf28d04ab..2c2ec926b 100644
--- a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py
+++ b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from holoviews.element import BoxWhisker
+from holoviews.plotting.mpl.util import MPL_GE_3_9
 
 from .test_plot import TestMPLPlot, mpl_renderer
 
@@ -13,7 +14,10 @@ class TestMPLBoxWhiskerPlot(TestMPLPlot):
         plot = mpl_renderer.get_plot(boxwhisker)
         data, style, axis_opts = plot.get_data(boxwhisker, {}, {})
         self.assertEqual(data[0][0], values)
-        self.assertEqual(style['labels'], [''])
+        if MPL_GE_3_9:
+            self.assertEqual(style['tick_labels'], [''])
+        else:
+            self.assertEqual(style['labels'], [''])
 
     def test_boxwhisker_simple_overlay(self):
         values = np.random.rand(100)
diff --git a/holoviews/tests/plotting/matplotlib/test_violinplot.py b/holoviews/tests/plotting/matplotlib/test_violinplot.py
index 8a1addf25..e87fdb539 100644
--- a/holoviews/tests/plotting/matplotlib/test_violinplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_violinplot.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from holoviews.element import Violin
+from holoviews.plotting.mpl.util import MPL_GE_3_9
 
 from .test_plot import TestMPLPlot, mpl_renderer
 
@@ -14,7 +15,10 @@ class TestMPLViolinPlot(TestMPLPlot):
         data, style, axis_opts = plot.get_data(violin, {}, {})
         self.assertEqual(data[0][0], values)
         self.assertEqual(style['positions'], [0])
-        self.assertEqual(style['labels'], [''])
+        if MPL_GE_3_9:
+            self.assertEqual(style['tick_labels'], [''])
+        else:
+            self.assertEqual(style['labels'], [''])
 
     def test_violin_simple_overlay(self):
         values = np.random.rand(100)
@@ -34,4 +38,7 @@ class TestMPLViolinPlot(TestMPLPlot):
         self.assertEqual(data[0][0], violin.select(A=0).dimension_values(1))
         self.assertEqual(data[0][1], violin.select(A=1).dimension_values(1))
         self.assertEqual(style['positions'], [0, 1])
-        self.assertEqual(style['labels'], ['0', '1'])
+        if MPL_GE_3_9:
+            self.assertEqual(style['tick_labels'], ['0', '1'])
+        else:
+            self.assertEqual(style['labels'], ['0', '1'])