Repository: yt-project/yt

Issue:
Question/Bug: matplotlib Qt backend specification
A question on yt slack reported the following error:

```
File ~/anaconda3/lib/python3.11/site-packages/yt/visualization/base_plot_types.py:137, in PlotMPL.__init__(self, fsize, axrect, norm_handler, figure, axes)
    134     self.axes = axes
    135 self.interactivity = get_interactivity()
--> 137 figure_canvas, figure_manager = self._get_canvas_classes()
    138 self.canvas = figure_canvas(self.figure)
    139 if figure_manager is not None:

TypeError: cannot unpack non-iterable NoneType object
```

The selected matplotlib backend in this instance is `QtAgg` (found with `import matplotlib;
print(str(matplotlib.get_backend())`),  so I think because yt's `BACKEND_SPECS` only contains explicitly versioned Qt entries as keys:

https://github.com/yt-project/yt/blob/d046de2a3a4fc2834b85956e7abc516f7b403376/yt/visualization/base_plot_types.py#L52-L53

`_get_canvas_classes` is catching a KeyError and returning  `None`, resulting in the above `TypeError` : 

https://github.com/yt-project/yt/blob/d046de2a3a4fc2834b85956e7abc516f7b403376/yt/visualization/base_plot_types.py#L152-L161

According to https://matplotlib.org/stable/api/backend_qt_api.html, specifying specific Qt backends is now discouraged, so I'm thinking `BACKEND_SPECS` needs a new `QtAgg` entry. Also looks like Qt4 support was dropped in matlab 3.5.0 (https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.5.0.html#qt-backends) and 3.5.0 is currently the minimum version for yt so I think we can just replace those 2 lines with a single `QtAgg` line?  That all sound sensible? I'm not super familiar with backends, so wanted to check before getting too deep... 



Discussion:
Is it necessary to specify a backend in general? I've heard matplotlib isn't great at finding defaults, but I know I've had to change the backend through an environment variable on at least one system to get (interactive?) plotting to work at all. If setting the backend explicitly is not necessary, maybe it would be better to let users set this if they have to, based on what works/is available on their system.
Well it already is possible for the user to set the matplotlib backend, but internally yt tries to do its own detection in order to get the proper canvas objects (based on whatever matplotlib detects). It may be that this whole architecture could be removed or refactored, but I think that'd be a much larger refactor (throughout many of the yt plot window classes) compared to just getting the generic QtAgg backend to work. 

Resolving patch:
diff --git a/yt/visualization/base_plot_types.py b/yt/visualization/base_plot_types.py
--- a/yt/visualization/base_plot_types.py
+++ b/yt/visualization/base_plot_types.py
@@ -49,8 +49,8 @@ class FormatKwargs(TypedDict):
     "GTKAgg": ["backend_gtkagg", "FigureCanvasGTKAgg", None],
     "GTKCairo": ["backend_gtkcairo", "FigureCanvasGTKCairo", None],
     "MacOSX": ["backend_macosx", "FigureCanvasMac", "FigureManagerMac"],
-    "Qt4Agg": ["backend_qt4agg", "FigureCanvasQTAgg", None],
     "Qt5Agg": ["backend_qt5agg", "FigureCanvasQTAgg", None],
+    "QtAgg": ["backend_qtagg", "FigureCanvasQTAgg", None],
     "TkAgg": ["backend_tkagg", "FigureCanvasTkAgg", None],
     "WX": ["backend_wx", "FigureCanvasWx", None],
     "WXAgg": ["backend_wxagg", "FigureCanvasWxAgg", None],
@@ -155,10 +155,7 @@ def _get_canvas_classes(self):
         else:
             key = "agg"
 
-        try:
-            module, fig_canvas, fig_manager = BACKEND_SPECS[key]
-        except KeyError:
-            return
+        module, fig_canvas, fig_manager = BACKEND_SPECS[key]
 
         mod = __import__(
             "matplotlib.backends",


Test patch: