Repository: librosa/librosa

Issue:
axes.get_lines.prop_cycler was made internal in matplotlib 3.8.0
**Describe the bug**

The access to `axes.get_lines.prop_cycler` has been made private in matplotlib 3.8.0, thereby breaking a number of tests.

https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042

```
librosa> FAILED tests/test_display.py::test_waveshow_mono - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_trans - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom_trans - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom_out - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_stereo - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_unknown_wavaxis - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_unknown_wavaxis - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_sharex_specshow_ms - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_sharex_waveplot_ms - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_disconnect - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_deladaptor - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
```

Upstream discussion: https://github.com/matplotlib/matplotlib/issues/26831
Upstream PR: https://github.com/matplotlib/matplotlib/pull/26341

**To Reproduce**

Run the test suite against matplotlib 3.8.0.

**Expected behavior**
Compatibility with matplotlib 3.8.0 as indicated by its version constraints.

**Software versions***
<!--
Please run the following Python code snippet and paste the output below.
```
>>> import platform; print(platform.platform())
Linux-6.1.46-x86_64-with-glibc2.37
>>> import sys; print("Python", sys.version)
Python 3.11.5 (main, Aug 24 2023, 12:23:19) [GCC 12.3.0]
>>> import numpy; print("NumPy", numpy.__version__)
NumPy 1.25.2
>>> import scipy; print("SciPy", scipy.__version__)
SciPy 1.11.2
>>> import librosa; print("librosa", librosa.__version__)
librosa 0.10.1
>>> 
>>> librosa.show_versions()
INSTALLED VERSIONS
------------------
python: 3.11.5 (main, Aug 24 2023, 12:23:19) [GCC 12.3.0]

librosa: 0.10.1

audioread: 3.0.0
numpy: 1.25.2
scipy: 1.11.2
sklearn: 1.3.0
joblib: 1.3.2
decorator: 5.1.1
numba: 0.58.0rc2
soundfile: 0.12.1
pooch: v1.7.0
soxr: 0.3.6
typing_extensions: installed, no version number available
lazy_loader: installed, no version number available
msgpack: 1.0.5

numpydoc: None
sphinx: None
sphinx_rtd_theme: None
matplotlib: 3.8.0
sphinx_multiversion: None
sphinx_gallery: None
mir_eval: None
ipython: None
sphinxcontrib.rsvgconverter: None
pytest: None
pytest_mpl: None
pytest_cov: None
samplerate: None
resampy: None
presets: None
packaging: 23.1
```
-->

**Additional context**
n/a

axes.get_lines.prop_cycler was made internal in matplotlib 3.8.0
**Describe the bug**

The access to `axes.get_lines.prop_cycler` has been made private in matplotlib 3.8.0, thereby breaking a number of tests.

https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042

```
librosa> FAILED tests/test_display.py::test_waveshow_mono - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_trans - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom_trans - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_mono_zoom_out - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_stereo - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_unknown_wavaxis - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_unknown_wavaxis - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_sharex_specshow_ms - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_sharex_waveplot_ms - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_disconnect - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
librosa> FAILED tests/test_display.py::test_waveshow_deladaptor - AttributeError: '_process_plot_var_args' object has no attribute 'prop_cycler'
```

Upstream discussion: https://github.com/matplotlib/matplotlib/issues/26831
Upstream PR: https://github.com/matplotlib/matplotlib/pull/26341

**To Reproduce**

Run the test suite against matplotlib 3.8.0.

**Expected behavior**
Compatibility with matplotlib 3.8.0 as indicated by its version constraints.

**Software versions***
<!--
Please run the following Python code snippet and paste the output below.
```
>>> import platform; print(platform.platform())
Linux-6.1.46-x86_64-with-glibc2.37
>>> import sys; print("Python", sys.version)
Python 3.11.5 (main, Aug 24 2023, 12:23:19) [GCC 12.3.0]
>>> import numpy; print("NumPy", numpy.__version__)
NumPy 1.25.2
>>> import scipy; print("SciPy", scipy.__version__)
SciPy 1.11.2
>>> import librosa; print("librosa", librosa.__version__)
librosa 0.10.1
>>> 
>>> librosa.show_versions()
INSTALLED VERSIONS
------------------
python: 3.11.5 (main, Aug 24 2023, 12:23:19) [GCC 12.3.0]

librosa: 0.10.1

audioread: 3.0.0
numpy: 1.25.2
scipy: 1.11.2
sklearn: 1.3.0
joblib: 1.3.2
decorator: 5.1.1
numba: 0.58.0rc2
soundfile: 0.12.1
pooch: v1.7.0
soxr: 0.3.6
typing_extensions: installed, no version number available
lazy_loader: installed, no version number available
msgpack: 1.0.5

numpydoc: None
sphinx: None
sphinx_rtd_theme: None
matplotlib: 3.8.0
sphinx_multiversion: None
sphinx_gallery: None
mir_eval: None
ipython: None
sphinxcontrib.rsvgconverter: None
pytest: None
pytest_mpl: None
pytest_cov: None
samplerate: None
resampy: None
presets: None
packaging: 23.1
```
-->

**Additional context**
n/a



Discussion:
Thanks for raising this.. we probably should have seen this coming :sweat: but I was hoping they'd implement a proper public API for this before doing a deprecation purge.

I think in the short term, we'll have to cut a post-release on 0.10.1 to upper-pin matplotlib at <3.8, and then we'll need to figure out how to replace the [offending code](https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042) going forward.


> and then we'll need to figure out how to replace the [offending code](https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042) going forward.

Gave this some thought, and I think we can get around this by revising the logic a little.  We're only accessing the cycler to get the color value so that it can be shared by the step and fill_between plots.  The logic goes like:

1. if we don't have a color, get the next color from the cycler and add it to kwargs
2. construct a step plot with kwargs
3. construct a fill_between plot with kwargs

Instead, we could imagine doing something like:
1. construct a step plot with kwargs
2. if color wasn't in kwargs already, pull the color from the newly constructed step plot and add it to kwargs
3. construct the fill_between

I think this should be a pretty simple change to make, if anyone wants to take a crack at it.  I won't have time for it myself for a few days.
Thanks for raising this.. we probably should have seen this coming :sweat: but I was hoping they'd implement a proper public API for this before doing a deprecation purge.

I think in the short term, we'll have to cut a post-release on 0.10.1 to upper-pin matplotlib at <3.8, and then we'll need to figure out how to replace the [offending code](https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042) going forward.


> and then we'll need to figure out how to replace the [offending code](https://github.com/librosa/librosa/blob/43d4427f7a389acc79b5be3c924646701f19bb46/librosa/display.py#L2041-L2042) going forward.

Gave this some thought, and I think we can get around this by revising the logic a little.  We're only accessing the cycler to get the color value so that it can be shared by the step and fill_between plots.  The logic goes like:

1. if we don't have a color, get the next color from the cycler and add it to kwargs
2. construct a step plot with kwargs
3. construct a fill_between plot with kwargs

Instead, we could imagine doing something like:
1. construct a step plot with kwargs
2. if color wasn't in kwargs already, pull the color from the newly constructed step plot and add it to kwargs
3. construct the fill_between

I think this should be a pretty simple change to make, if anyone wants to take a crack at it.  I won't have time for it myself for a few days.

Resolving patch:
diff --git a/librosa/display.py b/librosa/display.py
--- a/librosa/display.py
+++ b/librosa/display.py
@@ -2038,9 +2038,6 @@ def waveshow(
         version_removed="1.0",
     )
 
-    if "color" not in kwargs:
-        kwargs.setdefault("color", next(axes._get_lines.prop_cycler)["color"])
-
     # Reduce by envelope calculation
     # this choice of hop ensures that the envelope has at most max_points values
     hop_length = max(1, y.shape[-1] // max_points)
@@ -2064,6 +2061,10 @@ def waveshow(
 
     (steps,) = axes.step(xdata, ydata, marker=marker, where=where, **kwargs)
 
+    # Pull color property from the steps object, if we don't already have it
+    if "color" not in kwargs:
+        kwargs.setdefault("color", steps.get_color())
+
     envelope = filler(
         times[: len(y_top) * hop_length : hop_length],
         y_bottom,


Test patch: