Repository: matplotlib/matplotlib

Issue:
[Bug]: format_image_data on an image of only zeros produces a large number of zeros
### Bug summary

If you use imshow, and then mouseover the image, the value is listed at the bottom of the plot.
However, if the image contains only zeros, the result is a comical number of zeros.

### Code for reproduction

```Python
import matplotlib
import numpy as np
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
im = ax.imshow(np.zeros((4, 4)))
fig.show()
```


### Actual outcome

When you mouse-over the image it shows a large number of zeros (see bottom of image).
![image](https://github.com/user-attachments/assets/e1cb7d31-31a2-48f8-afd5-0dd57446ec38)

### Expected outcome

0.0 should be at the bottom of the page and the window should retain its original size:
![image](https://github.com/user-attachments/assets/25f03995-ea55-4eea-b9ee-1763513c7cbf)


### Additional information

test_image.py→test_image_cursor_formatting()  should test this perscise functionality, but there is a bug in this test where 
```py
    data = np.ma.masked_array([0], mask=[False])
    assert im.format_cursor_data(data) == '[0]'
```
should be
```py
    data = np.ma.masked_array(0, mask=[False])
    assert im.format_cursor_data(data) == '[0]'
```
And this causes `im.format_cursor_data` to fallback and '0' is returned. 
This test should be updated as part of a PR to fix this bug. 


### Operating system

_No response_

### Matplotlib Version

matplotlib_dev, 3.8.2

### Matplotlib Backend

TkAgg

### Python version

_No response_

### Jupyter version

_No response_

### Installation

git checkout


Discussion:
The issue is singular norms.  We do a bunch of extra logic it https://github.com/matplotlib/matplotlib/blob/adb97e848ca88f0b9abc5b1c5085f548847e3f8f/lib/matplotlib/artist.py#L1349-L1377 to estimate a "good" number of digits via https://github.com/matplotlib/matplotlib/blob/adb97e848ca88f0b9abc5b1c5085f548847e3f8f/lib/matplotlib/cbook.py#L2249-L2267


```
In [10]: import matplotlib.cbook as mc

In [11]: mc._g_sig_digits(0, 0)
Out[13]: 325

In [14]: mc._g_sig_digits(0, .1)
Out[14]: 2

In [15]: mc._g_sig_digits(0, 0)
Out[15]: 325

In [16]: mc._g_sig_digits(0, .00001)
Out[16]: 6

In [17]: mc._g_sig_digits(0, .000001)
Out[17]: 7

In [18]: mc._g_sig_digits(0, .0001)
Out[18]: 5

In [19]: mc._g_sig_digits(.1, 0)
Out[19]: 17
```

I have some ideas, will open a PR shortly.



Patch:
diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index 981365d852be..24aaa349ee05 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -1365,7 +1365,9 @@ def format_cursor_data(self, data):
                     delta = np.diff(
                         self.norm.boundaries[neigh_idx:cur_idx + 2]
                     ).max()
-
+                elif self.norm.vmin == self.norm.vmax:
+                    # singular norms, use delta of 10% of only value
+                    delta = np.abs(self.norm.vmin * .1)
                 else:
                     # Midpoints of neighboring color intervals.
                     neighbors = self.norm.inverse(
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index e4f60aac37a8..2411784af3ec 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -2252,6 +2252,10 @@ def _g_sig_digits(value, delta):
     it is known with an error of *delta*.
     """
     if delta == 0:
+        if value == 0:
+            # if both value and delta are 0, np.spacing below returns 5e-324
+            # which results in rather silly results
+            return 3
         # delta = 0 may occur when trying to format values over a tiny range;
         # in that case, replace it by the distance to the closest float.
         delta = abs(np.spacing(value))


Test patch:
diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py
index dfacfccb3e0e..6de1754f9ed7 100644
--- a/lib/matplotlib/tests/test_image.py
+++ b/lib/matplotlib/tests/test_image.py
@@ -390,7 +390,8 @@ def test_cursor_data_nonuniform(xy, data):
         ([[.123, .987]], "[0.123]"),
         ([[np.nan, 1, 2]], "[]"),
         ([[1, 1+1e-15]], "[1.0000000000000000]"),
-        ([[-1, -1]], "[-1.0000000000000000]"),
+        ([[-1, -1]], "[-1.0]"),
+        ([[0, 0]], "[0.00]"),
     ])
 def test_format_cursor_data(data, text):
     from matplotlib.backend_bases import MouseEvent