Repository: pyqtgraph/pyqtgraph

Issue:
Axis can be zoomed out until program becomes unresponsive
<!-- In the following, please describe your issue in detail! -->
When working in log mode, it is very easy to zoom out until `AxisItem` can no longer handle the magnitude of the numbers involved.
An easy fix might be to set a default limit on the maximum range?
<!-- If some of the sections do not apply, just remove them. -->

### Steps 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. -->
- Run the code below to show a plot in y-axis logarithmic mode.
- Point at the vertical axis.
- Spin the mousewheel down. 
- Spin it again. On my system, this increases the displayed range to more then 1E-300 to 1E+300. Any values outside this range display as "inf" and "0".
- The following warning starts appearing: `..\pyqtgraph\graphicsItems\AxisItem.py:845: RuntimeWarning: overflow encountered in power estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)]`
- Spin the wheel one more time.
- The only labels are now "0", "1" and "inf".
- The application becomes unresponsive. 
- If it recovers, spin down again, it will lock up completely eventually.

```python
import numpy as np
import pyqtgraph as pg
pg.mkQApp()
pw = pg.PlotWidget()
pw.setLogMode(x=False, y=True)
pw.show()

xxx = np.linspace(-3, 3, 601)
yyy = np.sinc(xxx)**2
yyy = (yyy * 16384).astype(np.int16).astype(np.float32) / 16384 # quantize to make nulls 0

item1 = pg.PlotDataItem(pen='r')
item1.setData(xxx,yyy)
pw.addItem(item1)
pg.exec()
```

### Expected behavior

The program should not become unresponsive.

Beyond that, I am hoping for comments :)
To me, it does not seem very useful to try and display values outside the +/-1.7E+308 (and E-308) range that a double can handle. So it might be sufficient not to allow any larger range than that, even if working in log mode?

### Tested environment(s)
```
sys.platform: win32
sys.version: 3.9.7 | packaged by conda-forge | (default, Sep 14 2021, 01:11:01) [MSC v.1916 64 bit (AMD64)]
qt bindings: PyQt5 5.12.3 Qt 5.12.9
```


Discussion:

Resolving patch:
diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py
--- a/pyqtgraph/graphicsItems/AxisItem.py
+++ b/pyqtgraph/graphicsItems/AxisItem.py
@@ -210,9 +210,15 @@ def setLogMode(self, log):
         If *log* is True, then ticks are displayed on a logarithmic scale and values
         are adjusted accordingly. (This is usually accessed by changing the log mode
         of a :func:`PlotItem <pyqtgraph.PlotItem.setLogMode>`)
+        The linked ViewBox will be informed of the change.
         """
         self.logMode = log
         self.picture = None
+        if self._linkedView is not None:
+            if self.orientation in ('top', 'bottom'):
+                self._linkedView().setLogMode('x', log)
+            elif self.orientation in ('left', 'right'):
+                self._linkedView().setLogMode('y', log)
         self.update()
 
     def setTickFont(self, font):
diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
--- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
+++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
@@ -164,11 +164,14 @@ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True,
             'wheelScaleFactor': -1.0 / 8.0,
 
             'background': None,
+            
+            'logMode': [False, False],
 
             # Limits
-            'limits': {
-                'xLimits': [None, None],   # Maximum and minimum visible X values
-                'yLimits': [None, None],   # Maximum and minimum visible Y values
+            # maximum value of double float is 1.7E+308, but internal caluclations exceed this limit before the range reaches it.
+            'limits': { 
+                'xLimits': [-1E307, +1E307],   # Maximum and minimum visible X values
+                'yLimits': [-1E307, +1E307],   # Maximum and minimum visible Y values
                 'xRange': [None, None],   # Maximum and minimum X range
                 'yRange': [None, None],   # Maximum and minimum Y range
                 }
@@ -485,6 +488,26 @@ def _resetTarget(self):
         # behavior (because the user is unaware of targetRange).
         if self.state['aspectLocked'] is False: # (interferes with aspect locking)
             self.state['targetRange'] = [self.state['viewRange'][0][:], self.state['viewRange'][1][:]]
+            
+    def _effectiveLimits(self):
+        # Determines restricted effective scaling range when in log mapping mode
+        if self.state['logMode'][0]:
+            xlimits = (# constrain to the +1.7E308 to 2.2E-308 range of double float values
+                max( self.state['limits']['xLimits'][0], -307.6 ),
+                min( self.state['limits']['xLimits'][1], +308.2 )
+            )
+        else:
+            xlimits = self.state['limits']['xLimits']
+        
+        if self.state['logMode'][1]: 
+            ylimits = (# constrain to the +1.7E308 to 2.2E-308 range of double float values
+                max( self.state['limits']['yLimits'][0], -307.6 ),
+                min( self.state['limits']['yLimits'][1], +308.2 )
+            )
+        else:
+            ylimits = self.state['limits']['yLimits']
+        # print('limits ', xlimits, ylimits) # diagnostic output should reflect additional limit in log mode
+        return (xlimits, ylimits)
 
     def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True):
         """
@@ -506,7 +529,6 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=Tru
         ================== =====================================================================
 
         """
-
         changes = {}   # axes
         setRequested = [False, False]
 
@@ -533,8 +555,10 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=Tru
             yOff = False if setRequested[1] else None
             self.enableAutoRange(x=xOff, y=yOff)
             changed.append(True)
-
-        limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
+            
+        limits = self._effectiveLimits()
+        # print('rng:limits ', limits) # diagnostic output should reflect additional limit in log mode
+        # limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
         minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]]
         maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]]
 
@@ -929,6 +953,15 @@ def setXLink(self, view):
     def setYLink(self, view):
         """Link this view's Y axis to another view. (see LinkView)"""
         self.linkView(self.YAxis, view)
+        
+    def setLogMode(self, axis, logMode):
+        """Informs ViewBox that log mode is active for the specified axis, so that the view range cen be restricted"""
+        if axis == 'x':
+            self.state['logMode'][0] = bool(logMode)
+            # print('x log mode', self.state['logMode'][0] )
+        elif axis == 'y':
+            self.state['logMode'][1] = bool(logMode)
+            # print('x log mode', self.state['logMode'][0] )
 
     def linkView(self, axis, view):
         """
@@ -1485,15 +1518,15 @@ def updateViewRange(self, forceX=False, forceY=False):
         aspect = self.state['aspectLocked']  # size ratio / view ratio
         tr = self.targetRect()
         bounds = self.rect()
-
-        limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
+        
+        limits = self._effectiveLimits()
+        # print('upd:limits ', limits) # diagnostic output should reflect additional limit in log mode
         minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]]
         maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]]
 
         for axis in [0, 1]:
             if limits[axis][0] is None and limits[axis][1] is None and minRng[axis] is None and maxRng[axis] is None:
                 continue
-
             # max range cannot be larger than bounds, if they are given
             if limits[axis][0] is not None and limits[axis][1] is not None:
                 if maxRng[axis] is not None:


Test patch: