Mid-way through overhaul. Proposed code path looks like:
setRange -> updateViewRange -> matrix dirty -> sigRangeChanged ... -> prepareForPaint -> updateAutoRange, updateMatrix if dirty
This commit is contained in:
parent
ab1b1c6adf
commit
a4103dd152
@ -12,6 +12,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
|
|||||||
"""
|
"""
|
||||||
_qtBaseClass = QtGui.QGraphicsObject
|
_qtBaseClass = QtGui.QGraphicsObject
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
|
self.__inform_view_on_changes = True
|
||||||
QtGui.QGraphicsObject.__init__(self, *args)
|
QtGui.QGraphicsObject.__init__(self, *args)
|
||||||
self.setFlag(self.ItemSendsGeometryChanges)
|
self.setFlag(self.ItemSendsGeometryChanges)
|
||||||
GraphicsItem.__init__(self)
|
GraphicsItem.__init__(self)
|
||||||
@ -20,7 +21,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
|
|||||||
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
|
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
|
||||||
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
|
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
|
||||||
self.parentChanged()
|
self.parentChanged()
|
||||||
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
|
if self.__inform_view_on_changes and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
|
||||||
self.informViewBoundsChanged()
|
self.informViewBoundsChanged()
|
||||||
|
|
||||||
## workaround for pyqt bug:
|
## workaround for pyqt bug:
|
||||||
|
@ -17,6 +17,10 @@ __all__ = ['ViewBox']
|
|||||||
class ChildGroup(ItemGroup):
|
class ChildGroup(ItemGroup):
|
||||||
|
|
||||||
sigItemsChanged = QtCore.Signal()
|
sigItemsChanged = QtCore.Signal()
|
||||||
|
def __init__(self, parent):
|
||||||
|
ItemGroup.__init__(self, parent)
|
||||||
|
# excempt from telling view when transform changes
|
||||||
|
self._GraphicsObject__inform_view_on_change = False
|
||||||
|
|
||||||
def itemChange(self, change, value):
|
def itemChange(self, change, value):
|
||||||
ret = ItemGroup.itemChange(self, change, value)
|
ret = ItemGroup.itemChange(self, change, value)
|
||||||
@ -195,6 +199,21 @@ class ViewBox(GraphicsWidget):
|
|||||||
def implements(self, interface):
|
def implements(self, interface):
|
||||||
return interface == 'ViewBox'
|
return interface == 'ViewBox'
|
||||||
|
|
||||||
|
def itemChange(self, change, value):
|
||||||
|
ret = GraphicsWidget.itemChange(self, change, value)
|
||||||
|
if change == self.ItemSceneChange:
|
||||||
|
scene = self.scene()
|
||||||
|
if scene is not None:
|
||||||
|
scene.sigPrepareForPaint.disconnect(self.prepareForPaint)
|
||||||
|
elif change == self.ItemSceneHasChanged:
|
||||||
|
scene = self.scene()
|
||||||
|
if scene is not None:
|
||||||
|
scene.sigPrepareForPaint.connect(self.prepareForPaint)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def prepareForPaint(self):
|
||||||
|
#print "prepare"
|
||||||
|
pass
|
||||||
|
|
||||||
def getState(self, copy=True):
|
def getState(self, copy=True):
|
||||||
"""Return the current state of the ViewBox.
|
"""Return the current state of the ViewBox.
|
||||||
@ -308,12 +327,14 @@ class ViewBox(GraphicsWidget):
|
|||||||
ch.setParentItem(None)
|
ch.setParentItem(None)
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
#print self.name, "ViewBox.resizeEvent", self.size()
|
print self.name, "ViewBox.resizeEvent", self.size()
|
||||||
#self.setRange(self.range, padding=0)
|
#self.setRange(self.range, padding=0)
|
||||||
|
x,y = self.targetRange()
|
||||||
|
self.setRange(xRange=x, yRange=y, padding=0)
|
||||||
self.linkedXChanged()
|
self.linkedXChanged()
|
||||||
self.linkedYChanged()
|
self.linkedYChanged()
|
||||||
self.updateAutoRange()
|
self.updateAutoRange()
|
||||||
self.updateMatrix()
|
#self.updateMatrix()
|
||||||
self.sigStateChanged.emit(self)
|
self.sigStateChanged.emit(self)
|
||||||
self.background.setRect(self.rect())
|
self.background.setRect(self.rect())
|
||||||
#self._itemBoundsCache.clear()
|
#self._itemBoundsCache.clear()
|
||||||
@ -357,77 +378,97 @@ class ViewBox(GraphicsWidget):
|
|||||||
Set the visible range of the ViewBox.
|
Set the visible range of the ViewBox.
|
||||||
Must specify at least one of *rect*, *xRange*, or *yRange*.
|
Must specify at least one of *rect*, *xRange*, or *yRange*.
|
||||||
|
|
||||||
============= =====================================================================
|
================== =====================================================================
|
||||||
**Arguments**
|
**Arguments**
|
||||||
*rect* (QRectF) The full range that should be visible in the view box.
|
*rect* (QRectF) The full range that should be visible in the view box.
|
||||||
*xRange* (min,max) The range that should be visible along the x-axis.
|
*xRange* (min,max) The range that should be visible along the x-axis.
|
||||||
*yRange* (min,max) The range that should be visible along the y-axis.
|
*yRange* (min,max) The range that should be visible along the y-axis.
|
||||||
*padding* (float) Expand the view by a fraction of the requested range.
|
*padding* (float) Expand the view by a fraction of the requested range.
|
||||||
By default, this value is set between 0.02 and 0.1 depending on
|
By default, this value is set between 0.02 and 0.1 depending on
|
||||||
the size of the ViewBox.
|
the size of the ViewBox.
|
||||||
============= =====================================================================
|
*update* (bool) If True, update the range of the ViewBox immediately.
|
||||||
|
Otherwise, the update is deferred until before the next render.
|
||||||
|
*disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left
|
||||||
|
unchanged.
|
||||||
|
================== =====================================================================
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print self.name, "ViewBox.setRange", rect, xRange, yRange, padding
|
print self.name, "ViewBox.setRange", rect, xRange, yRange, padding
|
||||||
|
|
||||||
changes = {}
|
changes = {} # axes
|
||||||
|
setRequested = [False, False]
|
||||||
|
|
||||||
if rect is not None:
|
if rect is not None:
|
||||||
changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]}
|
changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]}
|
||||||
|
setRequested = [True, True]
|
||||||
if xRange is not None:
|
if xRange is not None:
|
||||||
changes[0] = xRange
|
changes[0] = xRange
|
||||||
|
setRequested[0] = True
|
||||||
if yRange is not None:
|
if yRange is not None:
|
||||||
changes[1] = yRange
|
changes[1] = yRange
|
||||||
|
setRequested[1] = True
|
||||||
|
|
||||||
if len(changes) == 0:
|
if len(changes) == 0:
|
||||||
print(rect)
|
print(rect)
|
||||||
raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
|
raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
|
||||||
|
|
||||||
|
# Update axes one at a time
|
||||||
changed = [False, False]
|
changed = [False, False]
|
||||||
for ax, range in changes.items():
|
for ax, range in changes.items():
|
||||||
if padding is None:
|
|
||||||
xpad = self.suggestPadding(ax)
|
|
||||||
else:
|
|
||||||
xpad = padding
|
|
||||||
mn = min(range)
|
mn = min(range)
|
||||||
mx = max(range)
|
mx = max(range)
|
||||||
if mn == mx: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale.
|
|
||||||
|
# If we requested 0 range, try to preserve previous scale.
|
||||||
|
# Otherwise just pick an arbitrary scale.
|
||||||
|
if mn == mx:
|
||||||
dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0]
|
dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0]
|
||||||
if dy == 0:
|
if dy == 0:
|
||||||
dy = 1
|
dy = 1
|
||||||
mn -= dy*0.5
|
mn -= dy*0.5
|
||||||
mx += dy*0.5
|
mx += dy*0.5
|
||||||
xpad = 0.0
|
xpad = 0.0
|
||||||
if any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
|
|
||||||
raise Exception("Not setting range [%s, %s]" % (str(mn), str(mx)))
|
|
||||||
|
|
||||||
|
# Make sure no nan/inf get through
|
||||||
|
if not all(np.isfinite([mn, mx])):
|
||||||
|
raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx)))
|
||||||
|
|
||||||
|
# Apply padding
|
||||||
|
if padding is None:
|
||||||
|
xpad = self.suggestPadding(ax)
|
||||||
|
else:
|
||||||
|
xpad = padding
|
||||||
p = (mx-mn) * xpad
|
p = (mx-mn) * xpad
|
||||||
mn -= p
|
mn -= p
|
||||||
mx += p
|
mx += p
|
||||||
|
|
||||||
|
# Set target range
|
||||||
if self.state['targetRange'][ax] != [mn, mx]:
|
if self.state['targetRange'][ax] != [mn, mx]:
|
||||||
self.state['targetRange'][ax] = [mn, mx]
|
self.state['targetRange'][ax] = [mn, mx]
|
||||||
changed[ax] = True
|
changed[ax] = True
|
||||||
|
|
||||||
aspect = self.state['aspectLocked'] # size ratio / view ratio
|
# Update viewRange to match targetRange as closely as possible while
|
||||||
if aspect is not False and len(changes) == 1:
|
# accounting for aspect ratio constraint
|
||||||
## need to adjust orthogonal target range to match
|
lockX, lockY = setRequested
|
||||||
size = [self.width(), self.height()]
|
if lockX and lockY:
|
||||||
tr1 = self.state['targetRange'][ax]
|
lockX = False
|
||||||
tr2 = self.state['targetRange'][1-ax]
|
lockY = False
|
||||||
if size[1] == 0 or aspect == 0:
|
self.updateViewRange(lockX, lockY)
|
||||||
ratio = 1.0
|
|
||||||
else:
|
|
||||||
ratio = (size[0] / float(size[1])) / aspect
|
|
||||||
if ax == 0:
|
|
||||||
ratio = 1.0 / ratio
|
|
||||||
w = (tr1[1]-tr1[0]) * ratio
|
|
||||||
d = 0.5 * (w - (tr2[1]-tr2[0]))
|
|
||||||
self.state['targetRange'][1-ax] = [tr2[0]-d, tr2[1]+d]
|
|
||||||
|
|
||||||
|
# If ortho axes have auto-visible-only, update them now
|
||||||
|
# Note that aspect ratio constraints and auto-visible probably do not work together..
|
||||||
|
if changed[0] and self.state['autoVisibleOnly'][1]:
|
||||||
|
self.updateAutoRange() ## Maybe just indicate that auto range needs to be updated?
|
||||||
|
elif changed[1] and self.state['autoVisibleOnly'][0]:
|
||||||
|
self.updateAutoRange()
|
||||||
|
|
||||||
|
# If nothing has changed, we are done.
|
||||||
|
if not any(changed):
|
||||||
|
#if update and self.matrixNeedsUpdate:
|
||||||
|
#self.updateMatrix(changed)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Disable auto-range if needed
|
||||||
if any(changed) and disableAutoRange:
|
if disableAutoRange:
|
||||||
if all(changed):
|
if all(changed):
|
||||||
ax = ViewBox.XYAxes
|
ax = ViewBox.XYAxes
|
||||||
elif changed[0]:
|
elif changed[0]:
|
||||||
@ -436,26 +477,26 @@ class ViewBox(GraphicsWidget):
|
|||||||
ax = ViewBox.YAxis
|
ax = ViewBox.YAxis
|
||||||
self.enableAutoRange(ax, False)
|
self.enableAutoRange(ax, False)
|
||||||
|
|
||||||
|
|
||||||
self.sigStateChanged.emit(self)
|
self.sigStateChanged.emit(self)
|
||||||
|
|
||||||
self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect()))
|
# Update target rect for debugging
|
||||||
|
if self.target.isVisible():
|
||||||
|
self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect()))
|
||||||
|
|
||||||
if update and (any(changed) or self.matrixNeedsUpdate):
|
## Update view matrix only if requested
|
||||||
self.updateMatrix(changed)
|
#if update:
|
||||||
|
#self.updateMatrix(changed)
|
||||||
|
## Otherwise, indicate that the matrix needs to be updated
|
||||||
|
#else:
|
||||||
|
#self.matrixNeedsUpdate = True
|
||||||
|
|
||||||
if not update and any(changed):
|
## Inform linked views that the range has changed <<This should be moved>>
|
||||||
self.matrixNeedsUpdate = True
|
#for ax, range in changes.items():
|
||||||
|
#link = self.linkedView(ax)
|
||||||
|
#if link is not None:
|
||||||
|
#link.linkedViewChanged(self, ax)
|
||||||
|
|
||||||
for ax, range in changes.items():
|
|
||||||
link = self.linkedView(ax)
|
|
||||||
if link is not None:
|
|
||||||
link.linkedViewChanged(self, ax)
|
|
||||||
|
|
||||||
if changed[0] and self.state['autoVisibleOnly'][1]:
|
|
||||||
self.updateAutoRange()
|
|
||||||
elif changed[1] and self.state['autoVisibleOnly'][0]:
|
|
||||||
self.updateAutoRange()
|
|
||||||
|
|
||||||
def setYRange(self, min, max, padding=None, update=True):
|
def setYRange(self, min, max, padding=None, update=True):
|
||||||
"""
|
"""
|
||||||
@ -572,7 +613,7 @@ class ViewBox(GraphicsWidget):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def enableAutoRange(self, axis=None, enable=True):
|
def enableAutoRange(self, axis=None, enable=True, x=None, y=None):
|
||||||
"""
|
"""
|
||||||
Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both
|
Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both
|
||||||
(if *axis* is omitted, both axes will be changed).
|
(if *axis* is omitted, both axes will be changed).
|
||||||
@ -585,24 +626,39 @@ class ViewBox(GraphicsWidget):
|
|||||||
#import traceback
|
#import traceback
|
||||||
#traceback.print_stack()
|
#traceback.print_stack()
|
||||||
|
|
||||||
|
# support simpler interface:
|
||||||
|
if x is not None or y is not None:
|
||||||
|
if x is not None:
|
||||||
|
self.enableAutoRange(ViewBox.XAxis, x)
|
||||||
|
if y is not None:
|
||||||
|
self.enableAutoRange(ViewBox.YAxis, y)
|
||||||
|
return
|
||||||
|
|
||||||
if enable is True:
|
if enable is True:
|
||||||
enable = 1.0
|
enable = 1.0
|
||||||
|
|
||||||
if axis is None:
|
if axis is None:
|
||||||
axis = ViewBox.XYAxes
|
axis = ViewBox.XYAxes
|
||||||
|
|
||||||
|
needAutoRangeUpdate = False
|
||||||
|
|
||||||
if axis == ViewBox.XYAxes or axis == 'xy':
|
if axis == ViewBox.XYAxes or axis == 'xy':
|
||||||
self.state['autoRange'][0] = enable
|
axes = [0, 1]
|
||||||
self.state['autoRange'][1] = enable
|
|
||||||
elif axis == ViewBox.XAxis or axis == 'x':
|
elif axis == ViewBox.XAxis or axis == 'x':
|
||||||
self.state['autoRange'][0] = enable
|
axes = [0]
|
||||||
elif axis == ViewBox.YAxis or axis == 'y':
|
elif axis == ViewBox.YAxis or axis == 'y':
|
||||||
self.state['autoRange'][1] = enable
|
axes = [1]
|
||||||
else:
|
else:
|
||||||
raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.')
|
raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.')
|
||||||
|
|
||||||
if enable:
|
for ax in axes:
|
||||||
|
if self.state['autoRange'][ax] != enable:
|
||||||
|
self.state['autoRange'][ax] = enable
|
||||||
|
needAutoRangeUpdate |= (enable is not False)
|
||||||
|
|
||||||
|
if needAutoRangeUpdate:
|
||||||
self.updateAutoRange()
|
self.updateAutoRange()
|
||||||
|
|
||||||
self.sigStateChanged.emit(self)
|
self.sigStateChanged.emit(self)
|
||||||
|
|
||||||
def disableAutoRange(self, axis=None):
|
def disableAutoRange(self, axis=None):
|
||||||
@ -728,6 +784,7 @@ class ViewBox(GraphicsWidget):
|
|||||||
if oldLink is not None:
|
if oldLink is not None:
|
||||||
try:
|
try:
|
||||||
getattr(oldLink, signal).disconnect(slot)
|
getattr(oldLink, signal).disconnect(slot)
|
||||||
|
oldLink.sigResized.disconnect(slot)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
## This can occur if the view has been deleted already
|
## This can occur if the view has been deleted already
|
||||||
pass
|
pass
|
||||||
@ -738,6 +795,7 @@ class ViewBox(GraphicsWidget):
|
|||||||
else:
|
else:
|
||||||
self.state['linkedViews'][axis] = weakref.ref(view)
|
self.state['linkedViews'][axis] = weakref.ref(view)
|
||||||
getattr(view, signal).connect(slot)
|
getattr(view, signal).connect(slot)
|
||||||
|
view.sigResized.connect(slot)
|
||||||
if view.autoRangeEnabled()[axis] is not False:
|
if view.autoRangeEnabled()[axis] is not False:
|
||||||
self.enableAutoRange(axis, False)
|
self.enableAutoRange(axis, False)
|
||||||
slot()
|
slot()
|
||||||
@ -1233,47 +1291,126 @@ class ViewBox(GraphicsWidget):
|
|||||||
bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0])
|
bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0])
|
||||||
return bounds
|
return bounds
|
||||||
|
|
||||||
|
def updateViewRange(self, forceX=False, forceY=False):
|
||||||
|
## Update viewRange to match targetRange as closely as possible, given
|
||||||
|
## aspect ratio constraints.
|
||||||
|
|
||||||
|
viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
|
||||||
|
|
||||||
def updateMatrix(self, changed=None):
|
# Make correction for aspect ratio constraint
|
||||||
## Make the childGroup's transform match the requested range.
|
aspect = self.state['aspectLocked'] # size ratio / view ratio
|
||||||
|
|
||||||
if changed is None:
|
|
||||||
changed = [False, False]
|
|
||||||
changed = list(changed)
|
|
||||||
tr = self.targetRect()
|
tr = self.targetRect()
|
||||||
bounds = self.rect()
|
bounds = self.rect()
|
||||||
|
if aspect is not False and tr.width() != 0 and bounds.width() != 0:
|
||||||
## set viewRect, given targetRect and possibly aspect ratio constraint
|
|
||||||
aspect = self.state['aspectLocked']
|
|
||||||
if aspect is False or bounds.height() == 0:
|
|
||||||
self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
|
|
||||||
else:
|
|
||||||
## aspect is (widget w/h) / (view range w/h)
|
## aspect is (widget w/h) / (view range w/h)
|
||||||
|
|
||||||
|
|
||||||
## This is the view range aspect ratio we have requested
|
## This is the view range aspect ratio we have requested
|
||||||
targetRatio = tr.width() / tr.height()
|
targetRatio = tr.width() / tr.height()
|
||||||
## This is the view range aspect ratio we need to obey aspect constraint
|
## This is the view range aspect ratio we need to obey aspect constraint
|
||||||
viewRatio = (bounds.width() / bounds.height()) / aspect
|
viewRatio = (bounds.width() / bounds.height()) / aspect
|
||||||
|
|
||||||
if targetRatio > viewRatio:
|
# Decide which range to keep unchanged
|
||||||
|
print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange']
|
||||||
|
if all(setRequested):
|
||||||
|
ax = 0 if targetRatio > viewRatio else 1
|
||||||
|
print "ax:", ax
|
||||||
|
elif setRequested[0]:
|
||||||
|
ax = 0
|
||||||
|
else:
|
||||||
|
ax = 1
|
||||||
|
|
||||||
|
#### these should affect viewRange, not targetRange!
|
||||||
|
if ax == 0:
|
||||||
## view range needs to be taller than target
|
## view range needs to be taller than target
|
||||||
dy = 0.5 * (tr.width() / viewRatio - tr.height())
|
dy = 0.5 * (tr.width() / viewRatio - tr.height())
|
||||||
if dy != 0:
|
if dy != 0:
|
||||||
changed[1] = True
|
changed[1] = True
|
||||||
self.state['viewRange'] = [
|
self.state['targetRange'][1] = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
|
||||||
self.state['targetRange'][0][:],
|
changed[1] = True
|
||||||
[self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
## view range needs to be wider than target
|
## view range needs to be wider than target
|
||||||
dx = 0.5 * (tr.height() * viewRatio - tr.width())
|
dx = 0.5 * (tr.height() * viewRatio - tr.width())
|
||||||
if dx != 0:
|
if dx != 0:
|
||||||
changed[0] = True
|
changed[0] = True
|
||||||
self.state['viewRange'] = [
|
self.state['targetRange'][0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx]
|
||||||
[self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx],
|
changed[0] = True
|
||||||
self.state['targetRange'][1][:]
|
|
||||||
]
|
|
||||||
|
## need to adjust orthogonal target range to match
|
||||||
|
#size = [self.width(), self.height()]
|
||||||
|
#tr1 = self.state['targetRange'][ax]
|
||||||
|
#tr2 = self.state['targetRange'][1-ax]
|
||||||
|
#if size[1] == 0 or aspect == 0:
|
||||||
|
#ratio = 1.0
|
||||||
|
#else:
|
||||||
|
#ratio = (size[0] / float(size[1])) / aspect
|
||||||
|
#if ax == 0:
|
||||||
|
#ratio = 1.0 / ratio
|
||||||
|
#w = (tr1[1]-tr1[0]) * ratio
|
||||||
|
#d = 0.5 * (w - (tr2[1]-tr2[0]))
|
||||||
|
#self.state['targetRange'][1-ax] = [tr2[0]-d, tr2[1]+d]
|
||||||
|
#changed[1-ax] = True
|
||||||
|
|
||||||
|
self.state['viewRange'] = viewRange
|
||||||
|
|
||||||
|
# emit range change signals here!
|
||||||
|
if changed[0]:
|
||||||
|
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
|
||||||
|
if changed[1]:
|
||||||
|
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
||||||
|
if any(changed):
|
||||||
|
self.sigRangeChanged.emit(self, self.state['viewRange'])
|
||||||
|
|
||||||
|
# Inform linked views that the range has changed <<This should be moved>>
|
||||||
|
for ax, range in changes.items():
|
||||||
|
link = self.linkedView(ax)
|
||||||
|
if link is not None:
|
||||||
|
link.linkedViewChanged(self, ax)
|
||||||
|
|
||||||
|
self._matrixNeedsUpdate = True
|
||||||
|
|
||||||
|
def updateMatrix(self, changed=None):
|
||||||
|
## Make the childGroup's transform match the requested viewRange.
|
||||||
|
|
||||||
|
#if changed is None:
|
||||||
|
#changed = [False, False]
|
||||||
|
#changed = list(changed)
|
||||||
|
#tr = self.targetRect()
|
||||||
|
#bounds = self.rect()
|
||||||
|
|
||||||
|
## set viewRect, given targetRect and possibly aspect ratio constraint
|
||||||
|
#self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
|
||||||
|
|
||||||
|
#aspect = self.state['aspectLocked']
|
||||||
|
#if aspect is False or bounds.height() == 0:
|
||||||
|
#self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
|
||||||
|
#else:
|
||||||
|
### aspect is (widget w/h) / (view range w/h)
|
||||||
|
|
||||||
|
### This is the view range aspect ratio we have requested
|
||||||
|
#targetRatio = tr.width() / tr.height()
|
||||||
|
### This is the view range aspect ratio we need to obey aspect constraint
|
||||||
|
#viewRatio = (bounds.width() / bounds.height()) / aspect
|
||||||
|
|
||||||
|
#if targetRatio > viewRatio:
|
||||||
|
### view range needs to be taller than target
|
||||||
|
#dy = 0.5 * (tr.width() / viewRatio - tr.height())
|
||||||
|
#if dy != 0:
|
||||||
|
#changed[1] = True
|
||||||
|
#self.state['viewRange'] = [
|
||||||
|
#self.state['targetRange'][0][:],
|
||||||
|
#[self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
|
||||||
|
#]
|
||||||
|
#else:
|
||||||
|
### view range needs to be wider than target
|
||||||
|
#dx = 0.5 * (tr.height() * viewRatio - tr.width())
|
||||||
|
#if dx != 0:
|
||||||
|
#changed[0] = True
|
||||||
|
#self.state['viewRange'] = [
|
||||||
|
#[self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx],
|
||||||
|
#self.state['targetRange'][1][:]
|
||||||
|
#]
|
||||||
|
|
||||||
vr = self.viewRect()
|
vr = self.viewRect()
|
||||||
if vr.height() == 0 or vr.width() == 0:
|
if vr.height() == 0 or vr.width() == 0:
|
||||||
@ -1294,15 +1431,16 @@ class ViewBox(GraphicsWidget):
|
|||||||
|
|
||||||
self.childGroup.setTransform(m)
|
self.childGroup.setTransform(m)
|
||||||
|
|
||||||
if changed[0]:
|
# moved to viewRangeChanged
|
||||||
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
|
#if changed[0]:
|
||||||
if changed[1]:
|
#self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
|
||||||
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
#if changed[1]:
|
||||||
if any(changed):
|
#self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
||||||
self.sigRangeChanged.emit(self, self.state['viewRange'])
|
#if any(changed):
|
||||||
|
#self.sigRangeChanged.emit(self, self.state['viewRange'])
|
||||||
|
|
||||||
self.sigTransformChanged.emit(self) ## segfaults here: 1
|
self.sigTransformChanged.emit(self) ## segfaults here: 1
|
||||||
self.matrixNeedsUpdate = False
|
self._matrixNeedsUpdate = False
|
||||||
|
|
||||||
def paint(self, p, opt, widget):
|
def paint(self, p, opt, widget):
|
||||||
if self.border is not None:
|
if self.border is not None:
|
||||||
|
@ -15,39 +15,56 @@ ViewBox test cases:
|
|||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
app = pg.mkQApp()
|
app = pg.mkQApp()
|
||||||
win = pg.GraphicsWindow()
|
|
||||||
vb = win.addViewBox(name="image view")
|
|
||||||
vb.setAspectLocked()
|
|
||||||
p1 = win.addPlot(name="plot 1")
|
|
||||||
p2 = win.addPlot(name="plot 2", row=1, col=0)
|
|
||||||
win.ci.layout.setRowFixedHeight(1, 150)
|
|
||||||
win.ci.layout.setColumnFixedWidth(1, 150)
|
|
||||||
|
|
||||||
def viewsMatch():
|
|
||||||
r0 = pg.np.array(vb.viewRange())
|
|
||||||
r1 = pg.np.array(p1.vb.viewRange()[1])
|
|
||||||
r2 = pg.np.array(p2.vb.viewRange()[1])
|
|
||||||
match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all()
|
|
||||||
return match
|
|
||||||
|
|
||||||
p1.setYLink(vb)
|
|
||||||
p2.setXLink(vb)
|
|
||||||
print "link views match:", viewsMatch()
|
|
||||||
win.show()
|
|
||||||
print "show views match:", viewsMatch()
|
|
||||||
imgData = pg.np.zeros((10, 10))
|
imgData = pg.np.zeros((10, 10))
|
||||||
imgData[0] = 1
|
imgData[0] = 3
|
||||||
imgData[-1] = 1
|
imgData[-1] = 3
|
||||||
imgData[:,0] = 1
|
imgData[:,0] = 3
|
||||||
imgData[:,-1] = 1
|
imgData[:,-1] = 3
|
||||||
img = pg.ImageItem(imgData)
|
|
||||||
vb.addItem(img)
|
def testLinkWithAspectLock():
|
||||||
p1.plot(x=imgData.sum(axis=0), y=range(10))
|
global win, vb
|
||||||
p2.plot(x=range(10), y=imgData.sum(axis=1))
|
win = pg.GraphicsWindow()
|
||||||
print "add items views match:", viewsMatch()
|
vb = win.addViewBox(name="image view")
|
||||||
#p1.setAspectLocked()
|
vb.setAspectLocked()
|
||||||
#grid = pg.GridItem()
|
vb.enableAutoRange(x=False, y=False)
|
||||||
#vb.addItem(grid)
|
p1 = win.addPlot(name="plot 1")
|
||||||
|
p2 = win.addPlot(name="plot 2", row=1, col=0)
|
||||||
|
win.ci.layout.setRowFixedHeight(1, 150)
|
||||||
|
win.ci.layout.setColumnFixedWidth(1, 150)
|
||||||
|
|
||||||
|
def viewsMatch():
|
||||||
|
r0 = pg.np.array(vb.viewRange())
|
||||||
|
r1 = pg.np.array(p1.vb.viewRange()[1])
|
||||||
|
r2 = pg.np.array(p2.vb.viewRange()[1])
|
||||||
|
match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all()
|
||||||
|
return match
|
||||||
|
|
||||||
|
p1.setYLink(vb)
|
||||||
|
p2.setXLink(vb)
|
||||||
|
print "link views match:", viewsMatch()
|
||||||
|
win.show()
|
||||||
|
print "show views match:", viewsMatch()
|
||||||
|
img = pg.ImageItem(imgData)
|
||||||
|
vb.addItem(img)
|
||||||
|
vb.autoRange()
|
||||||
|
p1.plot(x=imgData.sum(axis=0), y=range(10))
|
||||||
|
p2.plot(x=range(10), y=imgData.sum(axis=1))
|
||||||
|
print "add items views match:", viewsMatch()
|
||||||
|
#p1.setAspectLocked()
|
||||||
|
#grid = pg.GridItem()
|
||||||
|
#vb.addItem(grid)
|
||||||
|
pg.QtGui.QApplication.processEvents()
|
||||||
|
pg.QtGui.QApplication.processEvents()
|
||||||
|
#win.resize(801, 600)
|
||||||
|
|
||||||
|
def testAspectLock():
|
||||||
|
global win, vb
|
||||||
|
win = pg.GraphicsWindow()
|
||||||
|
vb = win.addViewBox(name="image view")
|
||||||
|
vb.setAspectLocked()
|
||||||
|
img = pg.ImageItem(imgData)
|
||||||
|
vb.addItem(img)
|
||||||
|
|
||||||
|
|
||||||
#app.processEvents()
|
#app.processEvents()
|
||||||
@ -72,3 +89,7 @@ print "add items views match:", viewsMatch()
|
|||||||
#win.resize(600, 100)
|
#win.resize(600, 100)
|
||||||
#app.processEvents()
|
#app.processEvents()
|
||||||
#print vb.viewRange()
|
#print vb.viewRange()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
testLinkWithAspectLock()
|
||||||
|
Loading…
Reference in New Issue
Block a user