diff --git a/examples/ViewLimits.py b/examples/ViewLimits.py index c08bf77c34da401473c04b5c7cc4f95adec821ec..c8f0dd21333178b484f7c9fc33d20d5841db8c7c 100644 --- a/examples/ViewLimits.py +++ b/examples/ViewLimits.py @@ -5,7 +5,7 @@ import pyqtgraph as pg import numpy as np plt = pg.plot(np.random.normal(size=100), title="View limit example") -plt.centralWidget.vb.setLimits(xRange=[-100, 100], minRange=[0.1, None], maxRange=[50, None]) +plt.centralWidget.vb.setLimits(xMin=-20, xMax=120, minXRange=5, maxXRange=100) ## Start Qt event loop unless running in interactive mode or using pyside. diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/pyqtgraph/graphicsItems/GraphicsLayout.py index a40165221c35dc25595b9ff960ebb3f03b502a06..b83257360dd85ff861e45eed95bee3e2dd452a90 100644 --- a/pyqtgraph/graphicsItems/GraphicsLayout.py +++ b/pyqtgraph/graphicsItems/GraphicsLayout.py @@ -31,6 +31,15 @@ class GraphicsLayout(GraphicsWidget): #ret = GraphicsWidget.resizeEvent(self, ev) #print self.pos(), self.mapToDevice(self.rect().topLeft()) #return ret + + def setBorder(self, *args, **kwds): + """ + Set the pen used to draw border between cells. + + See :func:`mkPen <pyqtgraph.mkPen>` for arguments. + """ + self.border = fn.mkPen(*args, **kwds) + self.update() def nextRow(self): """Advance to next row for automatic item placement""" diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index d44672869a2458e89b47154b7a61a1ebd43351c1..0fe6cd53c35e4cd4366bc126c4c1889d8ca730fe 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -121,10 +121,10 @@ class ViewBox(GraphicsWidget): # Limits 'limits': { - 'xRange': [None, None], # Maximum and minimum visible X values - 'yRange': [None, None], # Maximum and minimum visible Y values - 'minRange': [None, None], # Minimum allowed range for both axes - 'maxRange': [None, None], # Maximum allowed range for both axes + 'xLimits': [None, None], # Maximum and minimum visible X values + 'yLimits': [None, None], # Maximum and minimum visible Y values + 'xRange': [None, None], # Maximum and minimum X range + 'yRange': [None, None], # Maximum and minimum Y range } } @@ -407,6 +407,13 @@ class ViewBox(GraphicsWidget): print("make qrectf failed:", self.state['targetRange']) raise + def _resetTarget(self): + # Reset target range to exactly match current view range. + # This is used during mouse interaction to prevent unpredictable + # 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 setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): """ Set the visible range of the ViewBox. @@ -585,27 +592,38 @@ class ViewBox(GraphicsWidget): """ Set limits that constrain the possible view ranges. + **Panning limits**. The following arguments define the region within the + viewbox coordinate system that may be accessed by panning the view. =========== ============================================================ - Arguments - xRange (min, max) limits for x-axis range - yRange (min, max) limits for y-axis range - minRange (x, y) minimum allowed span - maxRange (x, y) maximum allowed span - xMin Minimum allowed x-axis range - xMax Maximum allowed x-axis range - yMin Minimum allowed y-axis range - yMax Maximum allowed y-axis range + xMin Minimum allowed x-axis value + xMax Maximum allowed x-axis value + yMin Minimum allowed y-axis value + yMax Maximum allowed y-axis value + =========== ============================================================ + + **Scaling limits**. These arguments prevent the view being zoomed in or + out too far. =========== ============================================================ + minXRange Minimum allowed left-to-right span across the view. + maxXRange Maximum allowed left-to-right span across the view. + minYRange Minimum allowed top-to-bottom span across the view. + maxYRange Maximum allowed top-to-bottom span across the view. + =========== ============================================================ """ update = False - for kwd in ['xRange', 'yRange', 'minRange', 'maxRange']: - if kwd in kwds and self.state['limits'][kwd] != kwds[kwd]: - self.state['limits'][kwd] = kwds[kwd] - update = True + #for kwd in ['xLimits', 'yLimits', 'minRange', 'maxRange']: + #if kwd in kwds and self.state['limits'][kwd] != kwds[kwd]: + #self.state['limits'][kwd] = kwds[kwd] + #update = True for axis in [0,1]: for mnmx in [0,1]: kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx] + lname = ['xLimits', 'yLimits'][axis] + if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: + self.state['limits'][lname][mnmx] = kwds[kwd] + update = True + kwd = [['minXRange', 'maxXRange'], ['minYRange', 'maxYRange']][axis][mnmx] lname = ['xRange', 'yRange'][axis] if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: self.state['limits'][lname][mnmx] = kwds[kwd] @@ -1101,6 +1119,7 @@ class ViewBox(GraphicsWidget): center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) #center = ev.pos() + self._resetTarget() self.scaleBy(s, center) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) ev.accept() @@ -1158,6 +1177,7 @@ class ViewBox(GraphicsWidget): x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None + self._resetTarget() self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) elif ev.button() & QtCore.Qt.RightButton: @@ -1177,6 +1197,7 @@ class ViewBox(GraphicsWidget): y = s[1] if mouseEnabled[1] == 1 else None center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) + self._resetTarget() self.scaleBy(x=x, y=y, center=center) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) @@ -1372,9 +1393,9 @@ class ViewBox(GraphicsWidget): viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] changed = [False, False] - # Make correction for aspect ratio constraint + #-------- Make correction for aspect ratio constraint ---------- - ## aspect is (widget w/h) / (view range w/h) + # aspect is (widget w/h) / (view range w/h) aspect = self.state['aspectLocked'] # size ratio / view ratio tr = self.targetRect() bounds = self.rect() @@ -1409,25 +1430,27 @@ class ViewBox(GraphicsWidget): changed[0] = True viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] - # check for any requested limits - rng = (self.state['limits']['xRange'], self.state['limits']['yRange']) - minRng = self.state['limits']['minRange'][:] - maxRng = self.state['limits']['maxRange'][:] + # ----------- Make corrections for view limits ----------- + + 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]] for axis in [0, 1]: - if rng[axis][0] is None and rng[axis][1] is None and minRng[axis] is None and maxRng[axis] is None: + 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 rng[axis][0] is not None and rng[axis][1] is not None: + if limits[axis][0] is not None and limits[axis][1] is not None: if maxRng[axis] is not None: - maxRng[axis] = min(maxRng[axis], rng[axis][1]-rng[axis][0]) + maxRng[axis] = min(maxRng[axis], limits[axis][1]-limits[axis][0]) else: - maxRng[axis] = rng[axis][1]-rng[axis][0] + maxRng[axis] = limits[axis][1]-limits[axis][0] - #print "\nLimits for axis %d: range=%s min=%s max=%s" % (axis, rng[axis], minRng[axis], maxRng[axis]) + #print "\nLimits for axis %d: range=%s min=%s max=%s" % (axis, limits[axis], minRng[axis], maxRng[axis]) #print "Starting range:", viewRange[axis] + # Apply xRange, yRange diff = viewRange[axis][1] - viewRange[axis][0] if maxRng[axis] is not None and diff > maxRng[axis]: delta = maxRng[axis] - diff @@ -1443,19 +1466,22 @@ class ViewBox(GraphicsWidget): #print "after applying min/max:", viewRange[axis] - mn, mx = rng[axis] + # Apply xLimits, yLimits + mn, mx = limits[axis] if mn is not None and viewRange[axis][0] < mn: delta = mn - viewRange[axis][0] viewRange[axis][0] += delta viewRange[axis][1] += delta + changed[axis] = True elif mx is not None and viewRange[axis][1] > mx: delta = mx - viewRange[axis][1] viewRange[axis][0] += delta viewRange[axis][1] += delta + changed[axis] = True #print "after applying edge limits:", viewRange[axis] - changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) and (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] + changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] self.state['viewRange'] = viewRange # emit range change signals