Repository: matplotlib/matplotlib
Pull request: Give _DummyAxis instances a __name__
## PR Summary
`ScalarFormatter.format_data_short` uses `self.axis.__name__` to determine whether it is working with an x-axis, a y-axis, or something else, presumably a _DummyAxis instance.  This was failing in the z-cursor formatting of image-like plots for a the colorbar use case with boundaries specified.  This could be solved either by checking first for the presence of the `__name__` attribute or by giving _DummyAxis instances a name.  I chose the latter approach.

Closes #17624 
## PR Checklist

- [x] Has Pytest style unit tests
- [x] Code is [Flake 8](http://flake8.pycqa.org/en/latest/) compliant
- [ ] New features are documented, with examples if plot related
- [ ] Documentation is sphinx and numpydoc compliant
- [ ] Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
- [ ] Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

<!--
Thank you so much for your PR!  To help us review your contribution, please
consider the following points:

- A development guide is available at https://matplotlib.org/devdocs/devel/index.html.

- Help with git and github is available at
  https://matplotlib.org/devel/gitwash/development_workflow.html.

- Do not create the PR out of master, but out of a separate branch.

- The PR title should summarize the changes, for example "Raise ValueError on
  non-numeric input to set_xlim".  Avoid non-descriptive titles such as
  "Addresses issue #8576".

- The summary should provide at least 1-2 sentences describing the pull request
  in detail (Why is this change required?  What problem does it solve?) and
  link to any relevant issues.

- If you are contributing fixes to docstrings, please pay attention to
  http://matplotlib.org/devel/documenting_mpl.html#formatting.  In particular,
  note the difference between using single backquotes, double backquotes, and
  asterisks in the markup.

We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.
-->


Problem statement:
_DummyAxis needs a __name__ attribute for ScalarFormatter 
<!--To help us understand and resolve your issue, please fill out the form to the best of your ability.-->
<!--You can feel free to delete the sections that do not apply.-->

### Bug report

**Bug summary**

`ScalarFormatter.format_data_short(x)` uses `self.axis__name__` to get xaxis or yaxis info. If the  instance was made  with a `_DummyAxis` instance, however, it fails. 
**Code for reproduction**
See next comment.
**Matplotlib version**
Master (3.2.1.post2853+g15c0d7c39)

Hints from issue discussion:
This bug is triggered in normal usage by the following, if you move the cursor over the image:
```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

z = np.arange(9).reshape((3, 3)).astype(float)
fig, ax = plt.subplots()
im = ax.imshow(z)
cbar = fig.colorbar(im, ax=ax, boundaries=[2, 4, 6])
plt.show()
```

In this case, the `boundaries` kwarg is causing the use of a _DummyAxis, which lacks a `__name__` attribute.
The problem was noticed while looking at #17453.

Commit message: Handle axis instances even if they don't have a __name__.

Patch to review:
diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py
index 263df149de5d..611b5cf968d8 100644
--- a/lib/matplotlib/tests/test_ticker.py
+++ b/lib/matplotlib/tests/test_ticker.py
@@ -510,6 +510,14 @@ class TestScalarFormatter:
         (True, (6, 6), (-1e5, 1e5), 6, False),
     ]
 
+    cursor_data = [
+        [0., "0.000"],
+        [0.0123, "0.012"],
+        [0.123, "0.123"],
+        [1.23,  "1.230"],
+        [12.3, "12.300"],
+    ]
+
     @pytest.mark.parametrize('unicode_minus, result',
                              [(True, "\N{MINUS SIGN}1"), (False, "-1")])
     def test_unicode_minus(self, unicode_minus, result):
@@ -556,15 +564,21 @@ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
         tmp_form.set_locs(ax.yaxis.get_majorticklocs())
         assert orderOfMag == tmp_form.orderOfMagnitude
 
-    def test_cursor_precision(self):
+    @pytest.mark.parametrize('data, expected', cursor_data)
+    def test_cursor_precision(self, data, expected):
         fig, ax = plt.subplots()
         ax.set_xlim(-1, 1)  # Pointing precision of 0.001.
         fmt = ax.xaxis.get_major_formatter().format_data_short
-        assert fmt(0.) == "0.000"
-        assert fmt(0.0123) == "0.012"
-        assert fmt(0.123) == "0.123"
-        assert fmt(1.23) == "1.230"
-        assert fmt(12.3) == "12.300"
+        assert fmt(data) == expected
+
+    @pytest.mark.parametrize('data, expected', cursor_data)
+    def test_cursor_dummy_axis(self, data, expected):
+        # Issue #17624
+        sf = mticker.ScalarFormatter()
+        sf.create_dummy_axis()
+        sf.set_bounds(0, 10)
+        fmt = sf.format_data_short
+        assert fmt(data) == expected
 
 
 class FakeAxis:
diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py
index 31dbf601e531..5cef24be9934 100644
--- a/lib/matplotlib/ticker.py
+++ b/lib/matplotlib/ticker.py
@@ -198,6 +198,8 @@
 
 
 class _DummyAxis:
+    __name__ = "dummy"
+
     def __init__(self, minpos=0):
         self.dataLim = mtransforms.Bbox.unit()
         self.viewLim = mtransforms.Bbox.unit()
@@ -693,7 +695,8 @@ def format_data_short(self, value):
         if isinstance(value, Integral):
             fmt = "%d"
         else:
-            if self.axis.__name__ in ["xaxis", "yaxis"]:
+            if (hasattr(self.axis, '__name__') and
+                    self.axis.__name__ in ["xaxis", "yaxis"]):
                 if self.axis.__name__ == "xaxis":
                     axis_trf = self.axis.axes.get_xaxis_transform()
                     axis_inv_trf = axis_trf.inverted()
@@ -708,8 +711,8 @@ def format_data_short(self, value):
                         screen_xy + [[0, -1], [0, +1]])[:, 1]
                 delta = abs(neighbor_values - value).max()
             else:
-                # Rough approximation: no more than 1e4 pixels.
-                delta = self.axis.get_view_interval() / 1e4
+                # Rough approximation: no more than 1e4 divisions.
+                delta = np.diff(self.axis.get_view_interval()) / 1e4
             # If e.g. value = 45.67 and delta = 0.02, then we want to round to
             # 2 digits after the decimal point (floor(log10(0.02)) = -2);
             # 45.67 contributes 2 digits before the decimal point


Code review comments:
diff_hunk: @@ -693,7 +695,8 @@ def format_data_short(self, value):
         if isinstance(value, Integral):
             fmt = "%d"
         else:
-            if self.axis.__name__ in ["xaxis", "yaxis"]:
+            if (hasattr(self.axis, '__name__') and
+                    self.axis.__name__ in ["xaxis", "yaxis"]):
path: lib/matplotlib/ticker.py
text: @user1:
perhaps `getattr(self.axis, "__name__", "") in [...]`?

@user1:
can self merge with or without this