Repository: yt-project/yt

Issue:
BUG: symlog colorbar with linear threshold < vmin + matplotlib 3.5
### Bug report

**Bug summary**

**Code for reproduction**

```python
import matplotlib
import numpy as np
import yt

prng = np.random.RandomState(0)
d = {'my_fake_density': prng.random_integers(1, 3, size=(64,64,64))}

ds = yt.load_uniform_grid(d, [64,64,64])
p = yt.SlicePlot(ds, "z", ('stream', 'my_fake_density'))
p.set_log(('stream', 'my_fake_density'), True, linthresh=1.e-3)
p.save(f"/tmp/mpl_{matplotlib.__version__}_")
```

**Actual outcome**
![mpl_3 5 0rc1__Slice_z_my_fake_density](https://user-images.githubusercontent.com/14075922/137192964-313152ec-9190-4801-a49c-eaa54507ff42.png)


**Expected outcome**
This is the result of the same script, using matplotlib 3.4.3
![mpl_3 4 3__Slice_z_my_fake_density](https://user-images.githubusercontent.com/14075922/137192980-995a0d17-f16f-4ef3-bc3f-c707e30822a9.png)

Arguably, the behaviour with MPL 3.4 can be considered canonical, but maybe we need to add a warning when the value of `linthresh` is under the actual minimum value.

I note that this reference behaviour,  setting the lower limit of the colobar to the data's minimum rather than the user required linthresh, is in line with what can be obtained in both matplotlib 3.4 and 3.5 without yt:
```python
import matplotlib.pyplot as plt
from matplotlib.colors import SymLogNorm
import matplotlib
import numpy as np

prng = np.random.RandomState(0)
data = prng.random_integers(1, 3, size=(64,64))

fig, ax = plt.subplots()
im = ax.imshow(data, norm=SymLogNorm(linthresh=1e-2))
cb = fig.colorbar(im)
file = f"/tmp/threshold_mpl_{matplotlib.__version__}.png"
fig.savefig(file)
```
gives
![threshold_mpl_3 4 3](https://user-images.githubusercontent.com/14075922/137193701-dddfef81-1b3b-486f-9304-b5788315861a.png)



Discussion:
The fix is likely along the lines of preventing the norm class to be set to `SymLogNorm` when it doesn't make sense, do it as early as possible, and emit a warning.
I'll give it a go later

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
@@ -1,3 +1,4 @@
+import warnings
 from io import BytesIO
 
 import matplotlib
@@ -12,7 +13,7 @@
     mylog,
 )
 
-from ._commons import get_canvas, validate_image_name
+from ._commons import MPL_VERSION, get_canvas, validate_image_name
 
 BACKEND_SPECS = {
     "GTK": ["backend_gtk", "FigureCanvasGTK", "FigureManagerGTK"],
@@ -206,17 +207,28 @@ def _init_image(self, data, cbnorm, cblinthresh, cmap, extent, aspect):
             vmin=float(self.zmin) if self.zmin is not None else None,
             vmax=float(self.zmax) if self.zmax is not None else None,
         )
+        zmin = float(self.zmin) if self.zmin is not None else np.nanmin(data)
+        zmax = float(self.zmax) if self.zmax is not None else np.nanmax(data)
+
+        if cbnorm == "symlog":
+            # if cblinthresh is not specified, try to come up with a reasonable default
+            min_abs_val = np.min(np.abs((zmin, zmax)))
+            if cblinthresh is None:
+                cblinthresh = np.nanmin(np.absolute(data)[data != 0])
+            elif zmin * zmax > 0 and cblinthresh < min_abs_val:
+                warnings.warn(
+                    f"Cannot set a symlog norm with linear threshold {cblinthresh} "
+                    f"lower than the minimal absolute data value {min_abs_val} . "
+                    "Switching to log norm."
+                )
+                cbnorm = "log10"
+
         if cbnorm == "log10":
             cbnorm_cls = matplotlib.colors.LogNorm
         elif cbnorm == "linear":
             cbnorm_cls = matplotlib.colors.Normalize
         elif cbnorm == "symlog":
-            # if cblinthresh is not specified, try to come up with a reasonable default
-            if cblinthresh is None:
-                cblinthresh = np.nanmin(np.absolute(data)[data != 0])
-
             cbnorm_kwargs.update(dict(linthresh=cblinthresh))
-            MPL_VERSION = Version(matplotlib.__version__)
             if MPL_VERSION >= Version("3.2.0"):
                 # note that this creates an inconsistency between mpl versions
                 # since the default value previous to mpl 3.4.0 is np.e
@@ -273,8 +285,6 @@ def _init_image(self, data, cbnorm, cblinthresh, cmap, extent, aspect):
         if cbnorm == "symlog":
             formatter = matplotlib.ticker.LogFormatterMathtext(linthresh=cblinthresh)
             self.cb = self.figure.colorbar(self.image, self.cax, format=formatter)
-            zmin = float(self.zmin) if self.zmin is not None else np.nanmin(data)
-            zmax = float(self.zmax) if self.zmax is not None else np.nanmax(data)
 
             if zmin >= 0.0:
                 yticks = [zmin] + list(


Test patch: