Repository: qutip/qutip

Issue:
Matplotlib update broke qpt_plot_combined
### Bug Description

It seems that in new matplotlib version they separated classes `Axes` and `Axes3d`, so some plotting functions that have 3d aspects don't work as expected anymore. See example in the code below.

### Code to Reproduce the Bug

```shell
from qutip import qpt_plot_combined
qpt_plot_combined(np.eye(4), [["I", "X", "Y", "Z"]])
```


### Code Output

```shell
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[8], line 2
      1 from qutip import qpt_plot_combined
----> 2 qpt_plot_combined(np.eye(4), [["I", "X", "Y", "Z"]])

File ~\AppData\Local\pypoetry\Cache\virtualenvs\photonic-component-simulator-biULA8HA-py3.9\lib\site-packages\qutip\tomography.py:143, in qpt_plot_combined(chi, lbls_list, title, fig, ax, figsize, threshold)
    140 if not title:
    141     title = r"$\chi$"
--> 143 matrix_histogram_complex(chi, xlabels, xlabels, title=title, ax=ax,
    144                          threshold=threshold)
    146 return fig, ax

File ~\AppData\Local\pypoetry\Cache\virtualenvs\photonic-component-simulator-biULA8HA-py3.9\lib\site-packages\qutip\visualization.py:820, in matrix_histogram_complex(M, xlabels, ylabels, title, limits, phase_limits, colorbar, fig, ax, threshold)
    818 # x axis
    819 xtics = -0.5 + np.arange(M.shape[0])
--> 820 ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics))
    821 if xlabels:
    822     nxlabels = len(xlabels)

AttributeError: 'Axes3D' object has no attribute 'w_xaxis'
```


### Expected Behaviour

There should be a plot and not an error

### Your Environment

```shell
QuTiP vesion: '4.7.3'
matplotlib version: '3.8.0'
```


### Additional Context

_No response_


Discussion:
This seems to be because of this change: https://matplotlib.org/3.8.0/api/prev_api_changes/api_changes_3.8.0.html#axes3d

The solution from the docs:
> The w_xaxis, w_yaxis, and w_zaxis attributes are now removed. Instead use xaxis, yaxis, and zaxis.

Resolving patch:
diff --git a/qutip/visualization.py b/qutip/visualization.py
--- a/qutip/visualization.py
+++ b/qutip/visualization.py
@@ -480,7 +480,10 @@ def _update_yaxis(spacing, M, ax, ylabels):
     updates the y-axis
     """
     ytics = [x + (1 - (spacing / 2)) for x in range(M.shape[1])]
-    ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics))
+    if parse_version(mpl.__version__) >= parse_version("3.8"):
+        ax.axes.yaxis.set_major_locator(plt.FixedLocator(ytics))
+    else:
+        ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics))
     if ylabels:
         nylabels = len(ylabels)
         if nylabels != len(ytics):
@@ -498,7 +501,11 @@ def _update_xaxis(spacing, M, ax, xlabels):
     updates the x-axis
     """
     xtics = [x + (1 - (spacing / 2)) for x in range(M.shape[1])]
-    ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics))
+    if parse_version(mpl.__version__) >= parse_version("3.8"):
+        ax.axes.xaxis.set_major_locator(plt.FixedLocator(xtics))
+    else:
+        ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics))
+
     if xlabels:
         nxlabels = len(xlabels)
         if nxlabels != len(xtics):
@@ -515,7 +522,11 @@ def _update_zaxis(ax, z_min, z_max, zticks):
     """
     updates the z-axis
     """
-    ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5))
+    if parse_version(mpl.__version__) >= parse_version("3.8"):
+        ax.axes.zaxis.set_major_locator(plt.IndexLocator(1, 0.5))
+    else:
+        ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5))
+
     if isinstance(zticks, list):
         ax.set_zticks(zticks)
     ax.set_zlim3d([min(z_min, 0), z_max])
@@ -817,7 +828,10 @@ def matrix_histogram_complex(M, xlabels=None, ylabels=None,
 
     # x axis
     xtics = -0.5 + np.arange(M.shape[0])
-    ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics))
+    if parse_version(mpl.__version__) >= parse_version("3.8"):
+        ax.axes.xaxis.set_major_locator(plt.FixedLocator(xtics))
+    else:
+        ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics))
     if xlabels:
         nxlabels = len(xlabels)
         if nxlabels != len(xtics):
@@ -827,7 +841,10 @@ def matrix_histogram_complex(M, xlabels=None, ylabels=None,
 
     # y axis
     ytics = -0.5 + np.arange(M.shape[1])
-    ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics))
+    if parse_version(mpl.__version__) >= parse_version("3.8"):
+        ax.axes.yaxis.set_major_locator(plt.FixedLocator(ytics))
+    else:
+        ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics))
     if ylabels:
         nylabels = len(ylabels)
         if nylabels != len(ytics):


Test patch: