Repository: holoviz/holoviews

Issue:
Continous Bars no longer working
I'm trying to make a `bar` plot with hvplot using timeseries data. I would like to give positive values a green bar color and negative values a red bar color to make it very clear where the changes are positive and negative.

I've tried formatting my time as strings, ints and datatimes. I've tried arranging my data and using the `by` argument. I've tried setting the `color` as a list of colors. But I cannot achieve this either because it errors or it does not display as I want.

A workaround would be highly appreciated as this is the most used kindof plot in my domain.

## Reproducible Example

I believe the below is a bug.

```python
import panel as pn
import hvplot.pandas
from datetime import datetime
pn.extension()

KIND="bar"

positive_values = pd.DataFrame({
    "time": [datetime(year,1,1) for year in range(2025, 2033)],
    "value": list(range(1,9)),
})
neg_start=2024
negative_values = pd.DataFrame({
    "time": [datetime(2024,1,1)],
    "value": [-1]
})
pos_plot = positive_values.hvplot(x="time.year", y="value", color="green", kind=KIND)
neg_plot = negative_values.hvplot(x="time.year", y="value", color="red", kind=KIND)

plot = neg_plot*pos_plot

pn.panel(plot).servable()
```

```bash
Traceback (most recent call last):
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/reactive.py", line 432, in _change_coroutine
    self._change_event(doc)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/reactive.py", line 450, in _change_event
    self._process_events(events)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/reactive.py", line 1332, in _process_events
    self._update_selection(events.pop('indices'))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/widgets/tables.py", line 1641, in _update_selection
    self.selection = indices
    ^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f
    instance_param.__set__(obj, val)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 1553, in __set__
    obj.param._call_watcher(watcher, event)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2526, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2506, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/depends.py", line 108, in cb
    return func(*args, **dep_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/reactive.py", line 594, in wrapped
    return eval_fn()(*combined_args, **combined_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/src/mt_pm_reporting/reporting/ui/components/widgets.py", line 380, in _update_selection
    self.value = self.object.loc[selection[0], "key"]
    ^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f
    instance_param.__set__(obj, val)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 1553, in __set__
    obj.param._call_watcher(watcher, event)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2526, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2506, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/depends.py", line 108, in cb
    return func(*args, **dep_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/reactive.py", line 594, in wrapped
    return eval_fn()(*combined_args, **combined_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/src/mt_pm_reporting/reporting/ui/components/base.py", line 41, in update_other
    setattr(other, other_name, value)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f
    instance_param.__set__(obj, val)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 1553, in __set__
    obj.param._call_watcher(watcher, event)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2526, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2506, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 784, in _sync_caller
    return function()
           ^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/depends.py", line 53, in _depends
    return func(*args, **kw)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/src/mt_pm_reporting/reporting/ui/components/stores.py", line 119, in _update_total_change_by_time
    self.total_change_by_time = get_change_by_time(
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f
    instance_param.__set__(obj, val)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 1553, in __set__
    obj.param._call_watcher(watcher, event)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2526, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2506, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/param.py", line 897, in _replace_pane
    self._update_inner(new_object)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/pane/base.py", line 700, in _update_inner
    self._inner_layout[:] = [self._pane]
    ~~~~~~~~~~~~~~~~~~^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/layout/base.py", line 417, in __setitem__
    self.objects = new_objects
    ^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f
    instance_param.__set__(obj, val)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 1553, in __set__
    obj.param._call_watcher(watcher, event)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2526, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/param/parameterized.py", line 2506, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/reactive.py", line 375, in _param_change
    self._apply_update(named_events, properties, model, ref)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/reactive.py", line 303, in _apply_update
    self._update_model(events, msg, root, model, doc, comm)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/layout/base.py", line 106, in _update_model
    children, old_children = self._get_objects(model, old, doc, root, comm)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/layout/base.py", line 167, in _get_objects
    child = pane._get_model(doc, root, model, comm)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/layout/base.py", line 185, in _get_model
    objects, _ = self._get_objects(model, [], doc, root, comm)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/layout/base.py", line 167, in _get_objects
    child = pane._get_model(doc, root, model, comm)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/pane/holoviews.py", line 429, in _get_model
    plot = self._render(doc, comm, root)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/panel/pane/holoviews.py", line 525, in _render
    return renderer.get_plot(self.object, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/bokeh/renderer.py", line 68, in get_plot
    plot = super().get_plot(obj, doc, renderer, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/renderer.py", line 239, in get_plot
    plot.update(init_key)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/plot.py", line 956, in update
    return self.initialize_plot()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py", line 3339, in initialize_plot
    child = subplot.initialize_plot(ranges, plot, plots)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py", line 2130, in initialize_plot
    self._init_glyphs(plot, element, ranges, source)
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py", line 2046, in _init_glyphs
    with abbreviated_exception():
  File "/home/jovyan/repos/mt-pm-reporting/.venv/lib/python3.11/site-packages/holoviews/core/options.py", line 222, in __exit__
    raise AbbreviatedException(etype, value, traceback)
holoviews.core.options.AbbreviatedException: RuntimeError: 

Expected width to reference fields in the supplied data source.

When a 'source' argument is passed to a glyph method, values that are sequences
(like lists or arrays) must come from references to data columns in the source.

For instance, as an example:

    source = ColumnDataSource(data=dict(x=a_list, y=an_array))

    p.circle(x='x', y='y', source=source, ...) # pass column names and a source

Alternatively, *all* data sequences may be provided as literals as long as a
source is *not* provided:

    p.circle(x=a_list, y=an_array, ...)  # pass actual sequences and no source
```

As my data is dynamically generated, I might have 0, 1 or many positive values. I should be able to handle these situations. I had something working for all situations but 1 positive value. But after upgrading my environment everything stopped working.

With the old environment I believe there was a problem here

![image](https://github.com/holoviz/holoviews/assets/42288570/6891c67e-78fd-4304-a227-629202d28d28)

The problem was that the case when xvals consists of 1 element is not handled well. Now I don't know. The problem is the stack trace does not give me hints of the problem that I can understand.

If you change `KIND` to `"scatter"` it does not raise an exception.

![image](https://github.com/holoviz/holoviews/assets/42288570/b4722f23-721f-4b74-9016-d4e3dfaae1b6)



Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/holoviz/holoviews/pull/6301?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 [(`24f8da4`)](https://app.codecov.io/gh/holoviz/holoviews/commit/24f8da4bcb71d3c4fc38198e4897de69ba4c10e3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).
> Report is 1 commits behind head on main.

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


```diff
@@            Coverage Diff             @@
##             main    #6301      +/-   ##
==========================================
- Coverage   88.52%   88.52%   -0.01%     
==========================================
  Files         323      323              
  Lines       67996    68007      +11     
==========================================
+ Hits        60193    60202       +9     
- Misses       7803     7805       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/holoviz/holoviews/pull/6301?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/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/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'])