Repository: pyqtgraph/pyqtgraph

Issue:
Overflow when computing data bounds for small signed integer data types
### Short description
<!-- This should summarize the issue. -->
Data bounds are calculated using their underlying data type. For signed small integer data types where the data spans more than half the full-scale range, an exception occurs during computation of the data bounds. 

### Code to reproduce
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
     Ideally, this should be a full example someone else could run without additional setup. -->

```python
import pyqtgraph as pg
import numpy as np
pg.plot(np.array([-20000, 20000], dtype=np.int16))
pg.exec()
```

### Expected behavior
<!-- What should happen? -->
ViewBox should be auto-scaled around the data bounds.

### Real behavior
<!-- What happens? -->

```
c:\work\repos\pyqtgraph\pyqtgraph\graphicsItems\ViewBox\ViewBox.py:1464: RuntimeWarning: overflow encountered in short_scalars
  bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0])
```

### Tested environment(s)

 * PyQtGraph version: master
 * Qt Python binding: PyQt6 6.3.1
 * Python version: Python 3.10.8
 * NumPy version: 1.23.4
 * Operating system: Windows 11
 * Installation method: pip install -e .

### Additional context
This can of course be easily worked around by the user by simply converting the data to float32 prior to plot.


Discussion:

Resolving patch:
diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py
--- a/pyqtgraph/graphicsItems/PlotCurveItem.py
+++ b/pyqtgraph/graphicsItems/PlotCurveItem.py
@@ -295,13 +295,13 @@ def dataBounds(self, ax, frac=1.0, orthoRange=None):
             with warnings.catch_warnings(): 
                 # All-NaN data is acceptable; Explicit numpy warning is not needed.
                 warnings.simplefilter("ignore")
-                b = (np.nanmin(d), np.nanmax(d))
+                b = ( float(np.nanmin(d)), float(np.nanmax(d)) ) # enforce float format for bounds, even if data format is different
             if math.isinf(b[0]) or math.isinf(b[1]):
                 mask = np.isfinite(d)
                 d = d[mask]
                 if len(d) == 0:
                     return (None, None)
-                b = (d.min(), d.max())
+                b = ( float(d.min()), float(d.max()) ) # enforce float format for bounds, even if data format is different
 
         elif frac <= 0.0:
             raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
@@ -311,11 +311,14 @@ def dataBounds(self, ax, frac=1.0, orthoRange=None):
             d = d[mask]
             if len(d) == 0:
                 return (None, None)
-            b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)])
+            b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) # percentile result is always float64 or larger
 
         ## adjust for fill level
         if ax == 1 and self.opts['fillLevel'] not in [None, 'enclosed']:
-            b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel']))
+            b = ( 
+                float( min(b[0], self.opts['fillLevel']) ), 
+                float( max(b[1], self.opts['fillLevel']) )
+            ) # enforce float format for bounds, even if data format is different
 
         ## Add pen width only if it is non-cosmetic.
         pen = self.opts['pen']


Test patch:
diff --git a/tests/graphicsItems/test_PlotDataItem.py b/tests/graphicsItems/test_PlotDataItem.py
--- a/tests/graphicsItems/test_PlotDataItem.py
+++ b/tests/graphicsItems/test_PlotDataItem.py
@@ -11,12 +11,20 @@
 def test_bool():
     truths = np.random.randint(0, 2, size=(100,)).astype(bool)
     pdi = pg.PlotDataItem(truths)
-    bounds = pdi.dataBounds(1)
-    assert isinstance(bounds[0], np.uint8)
-    assert isinstance(bounds[1], np.uint8)
     xdata, ydata = pdi.getData()
     assert ydata.dtype == np.uint8
 
+def test_bound_formats():
+    for datatype in (bool, np.uint8, np.int16, float):
+        truths = np.random.randint(0, 2, size=(100,)).astype(datatype)
+        pdi_scatter = pg.PlotDataItem(truths, symbol='o', pen=None)
+        pdi_line    = pg.PlotDataItem(truths)
+        bounds = pdi_scatter.dataBounds(1)
+        assert isinstance(bounds[0], float), 'bound 0 is not float for scatter plot of '+str(datatype)
+        assert isinstance(bounds[0], float), 'bound 1 is not float for scatter plot of '+str(datatype)
+        bounds = pdi_line.dataBounds(1)
+        assert isinstance(bounds[0], float), 'bound 0 is not float for line plot of '+str(datatype)
+        assert isinstance(bounds[0], float), 'bound 1 is not float for line plot of '+str(datatype)
 
 def test_fft():
     f = 20.