Repository: matplotlib/matplotlib
Pull request: Check for float values for min/max values to ax{v,h}line
Fixes #12198. This now errors if a non-float value is provided for xmin/xmax or ymin/ymax to ax{v,h}{line,span}. This should get an API change or a what's new, but I'm not sure which, could someone advise? 

Problem statement:
axvline incorrectly tries to handle unitized ymin, ymax
After
```
line = plt.axvline(
    x=.5, ymin=matplotlib.dates.num2date(1), ymax=matplotlib.dates.num2date(1.05))
line.set_clip_on(False)
```
and panning the axes around, one sees that matplotlib (3.0) converted ymin and ymax to the scalar values 1 and 1.05, which is not correct: ymin and ymax are values relative to the axes bounds and thus intrinsically unitless; passing unitized data to ymin or ymax should result in an error.

The same point likely applies to axhline, axvspan, axhspan.

Hints from issue discussion:
This is still a bug...
![image](https://user-images.githubusercontent.com/4762399/62458715-9600a180-b7b8-11e9-99c9-9e3061506489.png)


I don't understand what panning has to do with this?
it's just to show that 1 and 1.05 have been interpreted as dates, not unitless axes-coordinates values.
But they haven't, they were converted back to a numerical value of 1/1.05 (which is probably still wrong). Panning around, the line always stays above the Axes.
Oops sorry, I guess I misremebered the example's behavior...  Well, backconversion to unitless values is also wrong :)

Commit message: Check for float values for min/max values to ax{v,h}line

Patch to review:
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index eaf94407e1ea..14a2c3692651 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -29,6 +29,7 @@
 import matplotlib.ticker as mticker
 import matplotlib.transforms as mtransforms
 import matplotlib.tri as mtri
+import matplotlib.units as munits
 from matplotlib import _preprocess_data, rcParams
 from matplotlib.axes._base import _AxesBase, _process_plot_format
 from matplotlib.axes._secondary_axes import SecondaryAxis
@@ -830,10 +831,10 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
 
             >>> axhline(y=.5, xmin=0.25, xmax=0.75)
         """
+        self._check_no_units([xmin, xmax], ['xmin', 'xmax'])
         if "transform" in kwargs:
-            raise ValueError(
-                "'transform' is not allowed as a kwarg;"
-                + "axhline generates its own transform.")
+            raise ValueError("'transform' is not allowed as a kwarg; "
+                             "axhline generates its own transform.")
         ymin, ymax = self.get_ybound()
 
         # We need to strip away the units for comparison with
@@ -899,11 +900,10 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
 
             >>> axvline(x=.5, ymin=0.25, ymax=0.75)
         """
-
+        self._check_no_units([ymin, ymax], ['ymin', 'ymax'])
         if "transform" in kwargs:
-            raise ValueError(
-                "'transform' is not allowed as a kwarg;"
-                + "axvline generates its own transform.")
+            raise ValueError("'transform' is not allowed as a kwarg; "
+                             "axvline generates its own transform.")
         xmin, xmax = self.get_xbound()
 
         # We need to strip away the units for comparison with
@@ -918,6 +918,14 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
         self._request_autoscale_view(scalex=scalex, scaley=False)
         return l
 
+    @staticmethod
+    def _check_no_units(vals, names):
+        # Helper method to check that vals are not unitized
+        for val, name in zip(vals, names):
+            if not munits._is_natively_supported(val):
+                raise ValueError(f"{name} must be a single scalar value, "
+                                 f"but got {val}")
+
     @docstring.dedent_interpd
     def axline(self, xy1, xy2=None, *, slope=None, **kwargs):
         """
@@ -1038,6 +1046,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
         --------
         axvspan : Add a vertical span across the axes.
         """
+        self._check_no_units([xmin, xmax], ['xmin', 'xmax'])
         trans = self.get_yaxis_transform(which='grid')
 
         # process the unit information
@@ -1098,6 +1107,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
         >>> axvspan(1.25, 1.55, facecolor='g', alpha=0.5)
 
         """
+        self._check_no_units([ymin, ymax], ['ymin', 'ymax'])
         trans = self.get_xaxis_transform(which='grid')
 
         # process the unit information
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index aaacda105e25..263d96491647 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -4251,6 +4251,22 @@ def test_vline_limit():
     assert_allclose(ax.get_ylim(), (-.1, .2))
 
 
+@pytest.mark.parametrize('fv, fh, args', [[plt.axvline, plt.axhline, (1,)],
+                                          [plt.axvspan, plt.axhspan, (1, 1)]])
+def test_axline_minmax(fv, fh, args):
+    bad_lim = matplotlib.dates.num2date(1)
+    # Check vertical functions
+    with pytest.raises(ValueError, match='ymin must be a single scalar value'):
+        fv(*args, ymin=bad_lim, ymax=1)
+    with pytest.raises(ValueError, match='ymax must be a single scalar value'):
+        fv(*args, ymin=1, ymax=bad_lim)
+    # Check horizontal functions
+    with pytest.raises(ValueError, match='xmin must be a single scalar value'):
+        fh(*args, xmin=bad_lim, xmax=1)
+    with pytest.raises(ValueError, match='xmax must be a single scalar value'):
+        fh(*args, xmin=1, xmax=bad_lim)
+
+
 def test_empty_shared_subplots():
     # empty plots with shared axes inherit limits from populated plots
     fig, axs = plt.subplots(nrows=1, ncols=2, sharex=True, sharey=True)


Code review comments:
diff_hunk: @@ -899,11 +900,10 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
 
             >>> axvline(x=.5, ymin=0.25, ymax=0.75)
         """
-
+        self._check_no_units([ymin, ymax], ['ymin', 'ymax'])
         if "transform" in kwargs:
-            raise ValueError(
-                "'transform' is not allowed as a kwarg;"
-                + "axvline generates its own transform.")
+            raise ValueError("'transform' is not allowed as a kwarg; "
path: lib/matplotlib/axes/_axes.py
text: @user1:
```suggestion
            raise ValueError("'transform' is not allowed as a keyword argument; "
```

diff_hunk: @@ -830,10 +831,10 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
 
             >>> axhline(y=.5, xmin=0.25, xmax=0.75)
         """
+        self._check_no_units([xmin, xmax], ['xmin', 'xmax'])
         if "transform" in kwargs:
-            raise ValueError(
-                "'transform' is not allowed as a kwarg;"
-                + "axhline generates its own transform.")
+            raise ValueError("'transform' is not allowed as a kwarg; "
path: lib/matplotlib/axes/_axes.py
text: @user1:
This should be spelled out:
```suggestion
            raise ValueError("'transform' is not allowed as a keyword argument; "
```