def wheelEvent(self, ev, axis=None): ev.accept() # s is actual scaling factor s = 1.02**(ev.delta() * self.state['wheelScaleFactor']) current_view = self.viewRect() #if only one node visible and wants to zoom in - not allowed if not (self.is_1_or_less(current_view) and s < 1): #if all visible and wants to zoom out - not allowed if self.is_all_visible(current_range=self.viewRange()) and s > 1: pass else: center = pg.QtCore.QPointF( fn.invertQTransform(self.childGroup.transform()).map( ev.pos())) if self.canopy_pespective: #changing center to ensure fixed canopy # max y-value of graph aka canopy max_y_coord = max(self.y_coords) center = pg.QtCore.QPointF(center.x(), max_y_coord) self._resetTarget() if (ev.modifiers() & QtCore.Qt.ShiftModifier): self.scaleBy(x=s, center=center) elif (ev.modifiers() & QtCore.Qt.ControlModifier): self.scaleBy(y=s, center=center) else: self.scaleBy(s=s, center=center)
def wheelEvent(self, ev, axis=None): if axis in (0, 1): mask = [False, False] mask[axis] = self.state['mouseEnabled'][axis] else: mask = self.state['mouseEnabled'][:] s = 1.02**(ev.delta() * self.state['wheelScaleFactor'] ) # actual scaling factor s = [(None if m is False else s) for m in mask] center = Point( fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) # JC added if ev.modifiers( ) == QtCore.Qt.ShiftModifier and s[0] is not None and s[1] is not None: for child in self.childGroup.childItems()[:]: if hasattr(child, 'accept_mousewheel_transformations'): m_old = child.transform() m = QtGui.QTransform() print(1, m_old.m22(), s[1]) m.scale(1, m_old.m22() * s[1]) child.setTransform(m) ev.accept() child_group_transformation = self.childGroup.transform() # self.childGroup.update() # self.autoRange() # self.updateAutoRange() # self.sigTransformChanged.emit(self) ## segfaults here: 1 print(self.viewRange()) print(self.targetRange()) return self._resetTarget() self.scaleBy(s, center) ev.accept() self.sigRangeChangedManually.emit(mask)
def wheelEvent(self, ev, axis=None): mask = np.array(self.state['mouseEnabled'], dtype=np.float) if axis is not None and axis >= 0 and axis < len(mask): mv = mask[axis] mask[:] = 0 mask[axis] = mv angleDelta = ev.angleDelta().y( ) if ev.angleDelta().y() != 0 else ev.angleDelta().x() if isinstance(ev, QWheelEvent): s = ((mask * 0.02) + 1)**(angleDelta * self.state['wheelScaleFactor'] ) # actual scaling factor else: s = ((mask * 0.02) + 1)**(ev.delta() * self.state['wheelScaleFactor'] ) # actual scaling factor 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()
def pixelHeight(self): ## deprecated vt = self.deviceTransform() if vt is None: return 0 vt = fn.invertQTransform(vt) return vt.map(QtCore.QLineF(0, 0, 0, 1)).length()
def wheelEvent(self, ev, axis=None): """Override wheel event so we manually track changes of zoom and update map accordingly.""" delta = 0.5 * np.sign(ev.delta()) self.__zoom_level = self.__clipped_zoom(self.__zoom_level + delta) center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) self.match_zoom(center, offset=True) ev.accept()
def mapRectFromDevice(self, rect): """ Return *rect* mapped from device coordinates (pixels) to local coordinates. If there is no device mapping available, return None. """ vt = self.deviceTransform() if vt is None: return None vt = fn.invertQTransform(vt) return vt.mapRect(rect)
def mouseDragEvent(self, ev, axis=None): ## Scale or translate based on mouse button ##leftdrag = pan || rightdrag = zoom if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): ev.accept() pos = ev.pos( ) #comes in terms of viewport position (topleft is 0, 0) lastPos = ev.lastPos() dif = self.mapToView(pos) - self.mapToView( lastPos) #converts to graph's coord #print(self.mapFromDevice((pos-lastPos))) self._resetTarget() if self.canopy_pespective: self.translateBy(x=-dif.x()) else: self.translateBy(x=-dif.x(), y=-dif.y()) elif ev.button() & QtCore.Qt.RightButton: ev.accept() dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = 1.02**dif current_view = self.viewRect() #if only one node visible and wants to zoom in - not allowed if not (self.is_1_or_less(current_view) and s[1] < 1): #if all visible and wants to zoom out - not allowed if self.is_all_visible( current_range=self.viewRange()) and s > 1: pass else: # center is calculated in graph coords tr = self.childGroup.transform() tr = fn.invertQTransform(tr) center = pg.QtCore.QPointF( tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) if self.canopy_pespective: #changing center to ensure fixed canopy # max y-value of graph aka canopy max_y_coord = max(self.y_coords) center = (center.x(), max_y_coord) self._resetTarget() if (ev.modifiers() & QtCore.Qt.ShiftModifier): self.scaleBy(x=s[1], center=center) elif (ev.modifiers() & QtCore.Qt.ControlModifier): self.scaleBy(y=s[1], center=center) else: #using y displacment for calculate both scales self.scaleBy(x=s[1], y=s[1], center=center)
def wheelEvent(self, ev, axis=None): """Override wheel event so we manually track changes of zoom and update map accordingly.""" if ev.delta() > 0: # zoom-in self.__zoom_level = self.__zoom_in_range(self.__zoom_level + 1) else: # zoom-out self.__zoom_level = self.__zoom_in_range(self.__zoom_level - 1) center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) self.match_zoom(center, offset=True) ev.accept()
def wheelEvent(self, ev, axis=None): mask = np.array(self.state['mouseEnabled'], dtype=np.float) if axis is not None and axis >= 0 and axis < len(mask): mv = mask[axis] mask[:] = 0 mask[axis] = mv s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) #center = ev.pos() self.scaleBy(s, center) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) ev.accept()
def mouseDragEvent(self, ev, axis=None): ## if axis is specified, event will only affect that axis. ev.accept() ## we accept all buttons pos = ev.pos() lastPos = ev.lastPos() dif = pos - lastPos dif = dif * -1 ## Ignore axes if mouse is disabled mask = np.array(self.state['mouseEnabled'], dtype=np.float) if axis is not None: mask[1-axis] = 0.0 ## Scale or translate based on mouse button if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): if self.state['mouseMode'] == ViewBox.RectMode: if ev.isFinish(): ## This is the final move in the drag; change the view scale now #print "finish" self.rbScaleBox.hide() #ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos)) ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) ax = self.childGroup.mapRectFromParent(ax) self.showAxRect(ax) self.axHistoryPointer += 1 self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] else: ## update shape of scale box self.updateScaleBox(ev.buttonDownPos(), ev.pos()) else: tr = dif*mask tr = self.mapToView(tr) - self.mapToView(Point(0,0)) self.translateBy(tr) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) elif ev.button() & QtCore.Qt.RightButton: #print "vb.rightDrag" if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1) ** dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) self.scaleBy(s, center) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
def mouseDragEvent(self, ev, axis = None): """If axis is specified, event will only affect that axis.""" ev.accept() ## we accept all buttons ## Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) mask = mouseEnabled.copy() if axis is not None: mask[1-axis] = 0.0 ## Scale or translate based on mouse button if ev.button() & QtCore.Qt.RightButton: dif = ev.screenPos() - ev.lastScreenPos() tr = self.childGroup.transform() tr = fn.invertQTransform(tr) self.scaleBy(x = None, y = ((mask[1] * 0.02) + 1) ** dif.y(), center=(0, 0)) #here mu change self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
def generateShape(self): ## determine rotation of transform #m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene. #mi = m.inverted()[0] dt = self.deviceTransform() if dt is None: self._shape = self.path return None v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) va = np.arctan2(v.y(), v.x()) dti = fn.invertQTransform(dt) devPos = dt.map(QtCore.QPointF(0, 0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) tr.rotate(va * 180. / 3.1415926) return dti.map(tr.map(self.path))
def generatePicture(self): self.picture = QtGui.QPicture() p = QtGui.QPainter() p.begin(self.picture) dt = fn.invertQTransform(self.viewTransform()) vr = self.getViewWidget().rect() unit = self.pixelWidth(), self.pixelHeight() dim = [vr.width(), vr.height()] lvr = self.boundingRect() ul = np.array([lvr.left(), lvr.top()]) br = np.array([lvr.right(), lvr.bottom()]) texts = [] if ul[1] > br[1]: x = ul[1] ul[1] = br[1] br[1] = x for i in [2, 1, 0]: ## Draw three different scales of grid dist = br - ul nlTarget = 10.0 ** i d = 10.0 ** np.floor(np.log10(abs(dist / nlTarget)) + 0.5) ul1 = np.floor(ul / d) * d br1 = np.ceil(br / d) * d dist = br1 - ul1 nl = (dist / d) + 0.5 # print "level", i # print " dim", dim # print " dist", dist # print " d", d # print " nl", nl for ax in range(0, 2): ## Draw grid for both axes ppl = dim[ax] / nl[ax] c = np.clip(3.0 * (ppl - 3), 0.0, 30.0) linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c * 2)) # linePen.setCosmetic(True) # linePen.setWidth(1) bx = (ax + 1) % 2 for x in range(0, int(nl[ax])): linePen.setCosmetic(False) if ax == 0: linePen.setWidthF(self.pixelWidth()) # print "ax 0 height", self.pixelHeight() else: linePen.setWidthF(self.pixelHeight()) # print "ax 1 width", self.pixelWidth() p.setPen(linePen) p1 = np.array([0.0, 0.0]) p2 = np.array([0.0, 0.0]) p1[ax] = ul1[ax] + x * d[ax] p2[ax] = p1[ax] p1[bx] = ul[bx] p2[bx] = br[bx] ## don't draw lines that are out of bounds. if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): continue p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) if i < 2: p.setPen(textPen) if ax == 0: x = p1[0] + unit[0] y = ul[1] + unit[1] * 8.0 else: x = ul[0] + unit[0] * 3 y = p1[1] + unit[1] texts.append((QtCore.QPointF(x, y), "%g" % p1[ax])) tr = self.deviceTransform() # tr.scale(1.5, 1.5) p.setWorldTransform(fn.invertQTransform(tr)) for t in texts: x = tr.map(t[0]) + Point(0.5, 0.5) p.drawText(x, t[1]) p.end()
def mouseDragEvent( self, ev, axis: Optional[int] = None, ) -> None: # if axis is specified, event will only affect that axis. ev.accept() # we accept all buttons button = ev.button() pos = ev.pos() lastPos = ev.lastPos() dif = pos - lastPos dif = dif * -1 # Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) mask = mouseEnabled.copy() if axis is not None: mask[1 - axis] = 0.0 # Scale or translate based on mouse button if button & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): # zoom y-axis ONLY when click-n-drag on it if axis == 1: # set a static y range special value on chart widget to # prevent sizing to data in view. self.chart._static_yrange = 'axis' scale_y = 1.3**(dif.y() * -1 / 20) self.setLimits(yMin=None, yMax=None) # print(scale_y) self.scaleBy((0, scale_y)) if self.state['mouseMode'] == ViewBox.RectMode: down_pos = ev.buttonDownPos() # This is the final position in the drag if ev.isFinish(): self.select_box.mouse_drag_released(down_pos, pos) # ax = QtCore.QRectF(down_pos, pos) # ax = self.childGroup.mapRectFromParent(ax) # print(ax) # this is the zoom transform cmd # self.showAxRect(ax) # self.axHistoryPointer += 1 # self.axHistory = self.axHistory[ # :self.axHistoryPointer] + [ax] else: self.select_box.set_pos(down_pos, pos) # update shape of scale box # self.updateScaleBox(ev.buttonDownPos(), ev.pos()) else: # default bevavior: click to pan view tr = self.childGroup.transform() tr = fn.invertQTransform(tr) tr = tr.map(dif * mask) - tr.map(Point(0, 0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) elif button & QtCore.Qt.RightButton: # right click zoom to center behaviour if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1)**dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None 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'])
def generatePicture(self): self.picture = QtGui.QPicture() p = QtGui.QPainter() p.begin(self.picture) dt = fn.invertQTransform(self.viewTransform()) vr = self.getViewWidget().rect() unit = self.pixelWidth(), self.pixelHeight() dim = [vr.width(), vr.height()] lvr = self.boundingRect() ul = np.array([lvr.left(), lvr.top()]) br = np.array([lvr.right(), lvr.bottom()]) texts = [] if ul[1] > br[1]: x = ul[1] ul[1] = br[1] br[1] = x for i in [2,1,0]: ## Draw three different scales of grid dist = br-ul nlTarget = 10.**i d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) ul1 = np.floor(ul / d) * d br1 = np.ceil(br / d) * d dist = br1-ul1 nl = (dist / d) + 0.5 #print "level", i #print " dim", dim #print " dist", dist #print " d", d #print " nl", nl for ax in range(0,2): ## Draw grid for both axes ppl = dim[ax] / nl[ax] c = np.clip(3.*(ppl-3), 0., 30.) linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) #linePen.setCosmetic(True) #linePen.setWidth(1) bx = (ax+1) % 2 for x in range(0, int(nl[ax])): linePen.setCosmetic(False) if ax == 0: linePen.setWidthF(self.pixelWidth()) #print "ax 0 height", self.pixelHeight() else: linePen.setWidthF(self.pixelHeight()) #print "ax 1 width", self.pixelWidth() p.setPen(linePen) p1 = np.array([0.,0.]) p2 = np.array([0.,0.]) p1[ax] = ul1[ax] + x * d[ax] p2[ax] = p1[ax] p1[bx] = ul[bx] p2[bx] = br[bx] ## don't draw lines that are out of bounds. if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): continue p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) if i < 2: p.setPen(textPen) if ax == 0: x = p1[0] + unit[0] y = ul[1] + unit[1] * 8. else: x = ul[0] + unit[0]*3 y = p1[1] + unit[1] texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) tr = self.deviceTransform() #tr.scale(1.5, 1.5) p.setWorldTransform(fn.invertQTransform(tr)) for t in texts: x = tr.map(t[0]) + Point(0.5, 0.5) p.drawText(x, t[1]) p.end()
def onMouseWheeled(self, ev, axis): """ Allows mouse wheel zoom on ctrl-click [currently] """ self.controlDrag = False # TODO: hack that should not be needed modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier or (modifiers == ( QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier)): # Emit a signal when the wheel is rotated alone and return a positive or negative value in self.progressBy # that we can use to incremement the image layer in the current axes if ev.delta() > 0: self.progressBy = 1 elif ev.delta() < 0: self.progressBy = -1 else: self.progressBy = 0 # this may be mouse-specific! self.progressBy = (self.progressBy * abs(ev.delta()) / 120) if modifiers == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier): # Allow faster scrolling if it was a shift+wheel self.progressBy = self.progressBy * 5 self.progressLayer.emit() else: # Handle zoom (mousewheel with no keyboard modifier) mask = np.array(self.state["mouseEnabled"], dtype=np.float) if axis is not None and 0 <= axis < len(mask): mv = mask[axis] mask[:] = 0 mask[axis] = mv # actual scaling factor s = ((mask * 0.02) + 1)**(ev.delta() * self.state["wheelScaleFactor"]) center = pg.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() if len(self.linkedAxis) > 0: for thisViewBox in self.linkedAxis: if self.linkedAxis[thisViewBox]["linkZoom"]: # Centre with the appropriate axes to avoid the views translating in horrible ways during zooming # I don't know why I also need to call my centerOn() method, but at least this works if (self.linkedAxis[thisViewBox]["linkX"] == "x" and self.linkedAxis[thisViewBox]["linkY"] is None): thisViewBox.scaleBy(s, x=center.x()) self.centreOn(thisViewBox, x=center.x()) if (self.linkedAxis[thisViewBox]["linkY"] == "y" and self.linkedAxis[thisViewBox]["linkX"] is None): thisViewBox.scaleBy(s, y=center.y()) self.centreOn(thisViewBox, y=center.y()) if (self.linkedAxis[thisViewBox]["linkX"] == "y" and self.linkedAxis[thisViewBox]["linkY"] is None): thisViewBox.scaleBy(s, y=center.x()) self.centreOn(thisViewBox, y=center.x()) if (self.linkedAxis[thisViewBox]["linkY"] == "x" and self.linkedAxis[thisViewBox]["linkX"] is None): thisViewBox.scaleBy(s, x=center.y()) self.centreOn(thisViewBox, x=center.y()) # The following two cases aren't used currently by Lasagna, but may be required in the future. # They haven't been tested yet. [28/07/15] if (self.linkedAxis[thisViewBox]["linkY"] == "x" and self.linkedAxis[thisViewBox]["linkX"] == "y"): thisViewBox.scaleBy(s, x=center.y(), y=center.x()) self.centreOn(thisViewBox, x=center.y(), y=center.x()) if (self.linkedAxis[thisViewBox]["linkY"] == "y" and self.linkedAxis[thisViewBox]["linkX"] == "x"): thisViewBox.scaleBy(s, x=center.x(), y=center.y()) self.centreOn(thisViewBox, x=center.x(), y=center.y())
def mapRectFromView(self, obj): vt = self.viewTransform() if vt is None: return None vt = fn.invertQTransform(vt) return vt.mapRect(obj)
def mapToView(self, obj): """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" m = fn.invertQTransform(self.childTransform()) return m.map(obj)
def pixelVectors(self, direction=None): """Return vectors in local coordinates representing the width and height of a view pixel. If direction is specified, then return vectors parallel and orthogonal to it. Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) or if pixel size is below floating-point precision limit. """ ## This is an expensive function that gets called very frequently. ## We have two levels of cache to try speeding things up. dt = self.deviceTransform() if dt is None: return None, None ## check local cache if direction is None and dt == self._pixelVectorCache[0]: return self._pixelVectorCache[1] ## check global cache key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) pv = self._pixelVectorGlobalCache.get(key, None) if pv is not None: self._pixelVectorCache = [dt, pv] return pv if direction is None: direction = QtCore.QPointF(1, 0) if direction.manhattanLength() == 0: raise Exception("Cannot compute pixel length for 0-length vector.") ## attempt to re-scale direction vector to fit within the precision of the coordinate system if direction.x() == 0: r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) #r = 1.0/(abs(dt.m12()) + abs(dt.m22())) elif direction.y() == 0: r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) #r = 1.0/(abs(dt.m11()) + abs(dt.m21())) else: r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 directionr = direction * r ## map direction vector onto device #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) #mdirection = dt.map(directionr) dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) viewDir = dt.map(dirLine) if viewDir.length() == 0: return None, None ## pixel size cannot be represented on this scale ## get unit vector and orthogonal vector (length of pixel) #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space try: normView = viewDir.unitVector() #normView = viewDir.norm() ## direction of one pixel orthogonal to line normOrtho = normView.normalVector() #normOrtho = orthoDir.norm() except: raise Exception("Invalid direction %s" %directionr) ## map back to item dti = fn.invertQTransform(dt) #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) self._pixelVectorCache[1] = pv self._pixelVectorCache[0] = dt self._pixelVectorGlobalCache[key] = pv return self._pixelVectorCache[1]
def mouseDragEvent( self, ev, axis: Optional[int] = None, relayed_from: ChartView = None, ) -> None: pos = ev.pos() lastPos = ev.lastPos() dif = pos - lastPos dif = dif * -1 # NOTE: if axis is specified, event will only affect that axis. button = ev.button() # Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) mask = mouseEnabled.copy() if axis is not None: mask[1-axis] = 0.0 # Scale or translate based on mouse button if button & ( QtCore.Qt.LeftButton | QtCore.Qt.MidButton ): # zoom y-axis ONLY when click-n-drag on it # if axis == 1: # # set a static y range special value on chart widget to # # prevent sizing to data in view. # self.chart._static_yrange = 'axis' # scale_y = 1.3 ** (dif.y() * -1 / 20) # self.setLimits(yMin=None, yMax=None) # # print(scale_y) # self.scaleBy((0, scale_y)) # SELECTION MODE if ( self.state['mouseMode'] == ViewBox.RectMode and axis is None ): # XXX: WHY ev.accept() down_pos = ev.buttonDownPos() # This is the final position in the drag if ev.isFinish(): self.select_box.mouse_drag_released(down_pos, pos) ax = QtCore.QRectF(down_pos, pos) ax = self.childGroup.mapRectFromParent(ax) # this is the zoom transform cmd self.showAxRect(ax) # axis history tracking self.axHistoryPointer += 1 self.axHistory = self.axHistory[ :self.axHistoryPointer] + [ax] else: print('drag finish?') self.select_box.set_pos(down_pos, pos) # update shape of scale box # self.updateScaleBox(ev.buttonDownPos(), ev.pos()) self.updateScaleBox( down_pos, ev.pos(), ) # PANNING MODE else: # XXX: WHY ev.accept() self.start_ic() # if self._ic is None: # self.chart.pause_all_feeds() # self._ic = trio.Event() if axis == 1: self.chart._static_yrange = 'axis' tr = self.childGroup.transform() tr = fn.invertQTransform(tr) tr = tr.map(dif*mask) - tr.map(Point(0, 0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) if ev.isFinish(): self.signal_ic() # self._ic.set() # self._ic = None # self.chart.resume_all_feeds() # WEIRD "RIGHT-CLICK CENTER ZOOM" MODE elif button & QtCore.Qt.RightButton: if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1) ** dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None 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']) # XXX: WHY ev.accept()
def pixelVectors(self, direction=None): """Return vectors in local coordinates representing the width and height of a view pixel. If direction is specified, then return vectors parallel and orthogonal to it. Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) or if pixel size is below floating-point precision limit. """ ## This is an expensive function that gets called very frequently. ## We have two levels of cache to try speeding things up. dt = self.deviceTransform() if dt is None: return None, None ## Ignore translation. If the translation is much larger than the scale ## (such as when looking at unix timestamps), we can get floating-point errors. dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) ## check local cache if direction is None and dt == self._pixelVectorCache[0]: return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* ## check global cache #key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) key = (dt.m11(), dt.m21(), dt.m12(), dt.m22()) pv = self._pixelVectorGlobalCache.get(key, None) if direction is None and pv is not None: self._pixelVectorCache = [dt, pv] return tuple(map(Point,pv)) ## return a *copy* if direction is None: direction = QtCore.QPointF(1, 0) if direction.manhattanLength() == 0: raise Exception("Cannot compute pixel length for 0-length vector.") ## attempt to re-scale direction vector to fit within the precision of the coordinate system ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. ## Example: ## dt = [ 1, 0, 2 ## 0, 2, 1e20 ## 0, 0, 1 ] ## Then we map the origin (0,0) and direction (0,1) and get: ## o' = 2,1e20 ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float ## ## |o' - d'| == 0 <-- this is the problem. ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? #if direction.x() == 0: #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) #elif direction.y() == 0: #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) #else: #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 #if r == 0: #r = 1. ## shouldn't need to do this; probably means the math above is wrong? #directionr = direction * r directionr = direction ## map direction vector onto device #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) #mdirection = dt.map(directionr) dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) viewDir = dt.map(dirLine) if viewDir.length() == 0: return None, None ## pixel size cannot be represented on this scale ## get unit vector and orthogonal vector (length of pixel) #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space try: normView = viewDir.unitVector() #normView = viewDir.norm() ## direction of one pixel orthogonal to line normOrtho = normView.normalVector() #normOrtho = orthoDir.norm() except: raise Exception("Invalid direction %s" %directionr) ## map back to item dti = fn.invertQTransform(dt) #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) self._pixelVectorCache[1] = pv self._pixelVectorCache[0] = dt self._pixelVectorGlobalCache[key] = pv return self._pixelVectorCache[1]
def pixelVectors(self, direction=None): """Return vectors in local coordinates representing the width and height of a view pixel. If direction is specified, then return vectors parallel and orthogonal to it. Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) or if pixel size is below floating-point precision limit. """ ## This is an expensive function that gets called very frequently. ## We have two levels of cache to try speeding things up. dt = self.deviceTransform() if dt is None: return None, None ## Ignore translation. If the translation is much larger than the scale ## (such as when looking at unix timestamps), we can get floating-point errors. dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) ## check local cache if direction is None and dt == self._pixelVectorCache[0]: return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* ## check global cache #key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) key = (dt.m11(), dt.m21(), dt.m12(), dt.m22()) pv = self._pixelVectorGlobalCache.get(key, None) if direction is None and pv is not None: self._pixelVectorCache = [dt, pv] return tuple(map(Point, pv)) ## return a *copy* if direction is None: direction = QtCore.QPointF(1, 0) if direction.manhattanLength() == 0: raise Exception("Cannot compute pixel length for 0-length vector.") ## attempt to re-scale direction vector to fit within the precision of the coordinate system ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. ## Example: ## dt = [ 1, 0, 2 ## 0, 2, 1e20 ## 0, 0, 1 ] ## Then we map the origin (0,0) and direction (0,1) and get: ## o' = 2,1e20 ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float ## ## |o' - d'| == 0 <-- this is the problem. ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? #if direction.x() == 0: #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) #elif direction.y() == 0: #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) #else: #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 #if r == 0: #r = 1. ## shouldn't need to do this; probably means the math above is wrong? #directionr = direction * r directionr = direction ## map direction vector onto device #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) #mdirection = dt.map(directionr) dirLine = QtCore.QLineF(QtCore.QPointF(0, 0), directionr) viewDir = dt.map(dirLine) if viewDir.length() == 0: return None, None ## pixel size cannot be represented on this scale ## get unit vector and orthogonal vector (length of pixel) #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space try: normView = viewDir.unitVector() #normView = viewDir.norm() ## direction of one pixel orthogonal to line normOrtho = normView.normalVector() #normOrtho = orthoDir.norm() except: raise Exception("Invalid direction %s" % directionr) ## map back to item dti = fn.invertQTransform(dt) #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) self._pixelVectorCache[1] = pv self._pixelVectorCache[0] = dt self._pixelVectorGlobalCache[key] = pv return self._pixelVectorCache[1]
def wheelEvent( self, ev, axis=None, relayed_from: ChartView = None, ): ''' Override "center-point" location for scrolling. This is an override of the ``ViewBox`` method simply changing the center of the zoom to be the y-axis. TODO: PR a method into ``pyqtgraph`` to make this configurable ''' if axis in (0, 1): mask = [False, False] mask[axis] = self.state['mouseEnabled'][axis] else: mask = self.state['mouseEnabled'][:] chart = self.linkedsplits.chart # don't zoom more then the min points setting l, lbar, rbar, r = chart.bars_range() vl = r - l if ev.delta() > 0 and vl <= _min_points_to_show: log.debug("Max zoom bruh...") return if ev.delta() < 0 and vl >= len(chart._arrays[chart.name]) + 666: log.debug("Min zoom bruh...") return # actual scaling factor s = 1.015 ** (ev.delta() * -1 / 20) # self.state['wheelScaleFactor']) s = [(None if m is False else s) for m in mask] if ( # zoom happened on axis axis == 1 # if already in axis zoom mode then keep it or self.chart._static_yrange == 'axis' ): self.chart._static_yrange = 'axis' self.setLimits(yMin=None, yMax=None) # print(scale_y) # pos = ev.pos() # lastPos = ev.lastPos() # dif = pos - lastPos # dif = dif * -1 center = Point( fn.invertQTransform( self.childGroup.transform() ).map(ev.pos()) ) # scale_y = 1.3 ** (center.y() * -1 / 20) self.scaleBy(s, center) else: # center = pg.Point( # fn.invertQTransform(self.childGroup.transform()).map(ev.pos()) # ) # XXX: scroll "around" the right most element in the view # which stays "pinned" in place. # furthest_right_coord = self.boundingRect().topRight() # yaxis = pg.Point( # fn.invertQTransform( # self.childGroup.transform() # ).map(furthest_right_coord) # ) # This seems like the most "intuitive option, a hybrid of # tws and tv styles last_bar = pg.Point(int(rbar)) + 1 ryaxis = chart.getAxis('right') r_axis_x = ryaxis.pos().x() end_of_l1 = pg.Point( round( chart.cv.mapToView( pg.Point(r_axis_x - chart._max_l1_line_len) # QPointF(chart._max_l1_line_len, 0) ).x() ) ) # .x() # self.state['viewRange'][0][1] = end_of_l1 # focal = pg.Point((last_bar.x() + end_of_l1)/2) focal = min( last_bar, end_of_l1, key=lambda p: p.x() ) # focal = pg.Point(last_bar.x() + end_of_l1) self._resetTarget() self.scaleBy(s, focal) self.sigRangeChangedManually.emit(mask) # self._ic.set() # self._ic = None # self.chart.resume_all_feeds() ev.accept()
def mouseDragEvent(self, ev, axis=None): # if axis is specified, event will only affect that axis. ev.accept() # we accept all buttons pos = ev.pos() lastPos = ev.lastPos() dif = pos - lastPos dif = dif * -1 # Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) mask = mouseEnabled.copy() if axis is not None: mask[1 - axis] = 0.0 # Scale or translate based on mouse button if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): if self.state['mouseMode'] == ViewBox.RectMode: down_pos = ev.buttonDownPos() # This is the final position in the drag if ev.isFinish(): self.select_box.mouse_drag_released(down_pos, pos) # ax = QtCore.QRectF(down_pos, pos) # ax = self.childGroup.mapRectFromParent(ax) # print(ax) # this is the zoom transform cmd # self.showAxRect(ax) # self.axHistoryPointer += 1 # self.axHistory = self.axHistory[ # :self.axHistoryPointer] + [ax] else: self.select_box.set_pos(down_pos, pos) # update shape of scale box # self.updateScaleBox(ev.buttonDownPos(), ev.pos()) else: tr = self.childGroup.transform() tr = fn.invertQTransform(tr) tr = tr.map(dif * mask) - tr.map(Point(0, 0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) elif ev.button() & QtCore.Qt.RightButton: # print "vb.rightDrag" if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1)**dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None 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'])
def mouseDragEvent(self, ev, axis=None): """Mouse drag tracker This function keep track of the mouse position and overwrites it to take the draw mode into account. Args: ev: signal emitted when user releases a mouse button. """ # Overwritting mouseDragEvent to take drawmode into account. ev.accept() pos = ev.pos() lastPos = ev.lastPos() dif = pos - lastPos dif = dif * -1 # Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) mask = mouseEnabled.copy() if axis is not None: mask[1 - axis] = 0.0 # If in drawing mode (editted part): if self.drawing: self.state['mouseMode'] = self.RectMode # If right button is selected draw zoom in boxes: if ev.button() & QtCore.Qt.RightButton: if ev.isFinish(): self.rbScaleBox.hide() ax = QtCore.QRectF( Point(ev.buttonDownPos(ev.button())), Point(pos)) ax = self.childGroup.mapRectFromParent(ax) self.showAxRect(ax) self.axHistoryPointer += 1 self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] else: self.updateScaleBox(ev.buttonDownPos(), ev.pos()) # If Left Button is selected drag image (This will be overwritten in the image by the drawing kernel) elif ev.button() & QtCore.Qt.LeftButton: tr = dif * mask tr = self.mapToView(tr) - self.mapToView(Point(0, 0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) # If Middle Button (wheel) zoom in or out. elif ev.button() & QtCore.Qt.MidButton: if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1) ** dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None y = s[1] if mouseEnabled[1] == 1 else None center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.LeftButton))) self._resetTarget() self.scaleBy(x=x, y=y, center=center) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) # If not in drawing mode: (original functionality) else: # Scale or translate based on mouse button if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): if self.state['mouseMode'] == ViewBox.RectMode: if ev.isFinish(): # This is the final move in the drag; change the view scale now # print "finish" self.rbScaleBox.hide() ax = QtCore.QRectF( Point(ev.buttonDownPos(ev.button())), Point(pos)) ax = self.childGroup.mapRectFromParent(ax) self.showAxRect(ax) self.axHistoryPointer += 1 self.axHistory = self.axHistory[:self.axHistoryPointer] + [ ax] else: # update shape of scale box self.updateScaleBox(ev.buttonDownPos(), ev.pos()) else: tr = dif * mask tr = self.mapToView(tr) - self.mapToView(Point(0, 0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit( self.state['mouseEnabled']) elif ev.button() & QtCore.Qt.RightButton: # print "vb.rightDrag" if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1) ** dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None 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'])
def onMouseWheeled(self, ev, axis): """ Allows mouse wheel zoom on ctrl-click [currently] """ self.controlDrag=False #TODO: hack that should not be needed modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier or (modifiers == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier)): # Emit a signal when the wheel is rotated alone and return a positive or negative value in self.progressBy # that we can use to incremement the image layer in the current axes if ev.delta()>0: self.progressBy=1 elif ev.delta()<0: self.progressBy=-1 else: self.progressBy=0 self.progressBy = self.progressBy * abs(ev.delta())/120 #this may be mouse-specific! if modifiers == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier): # Allow faster scrolling if it was a shift+wheel self.progressBy = self.progressBy*5 self.progressLayer.emit() else: #Handle zoom (mousewheel with no keyboard modifier) mask = np.array(self.state['mouseEnabled'], dtype=np.float) if axis is not None and axis >= 0 and axis < len(mask): mv = mask[axis] mask[:] = 0 mask[axis] = mv s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor center = pg.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() if len(self.linkedAxis)>0: for thisViewBox in self.linkedAxis: if self.linkedAxis[thisViewBox]['linkZoom']==True: #Centre with the appropriate axes to avoid the views translating in horrible ways during zooming #I don't know why I also need to call my centerOn() method, but at least this works if self.linkedAxis[thisViewBox]['linkX']=='x' and self.linkedAxis[thisViewBox]['linkY'] is None : thisViewBox.scaleBy(s,x=center.x()) self.centreOn(thisViewBox,x=center.x()) if self.linkedAxis[thisViewBox]['linkY']=='y' and self.linkedAxis[thisViewBox]['linkX'] is None : thisViewBox.scaleBy(s,y=center.y()) self.centreOn(thisViewBox,y=center.y()) if self.linkedAxis[thisViewBox]['linkX']=='y' and self.linkedAxis[thisViewBox]['linkY'] is None : thisViewBox.scaleBy(s,y=center.x()) self.centreOn(thisViewBox,y=center.x()) if self.linkedAxis[thisViewBox]['linkY']=='x' and self.linkedAxis[thisViewBox]['linkX'] is None : thisViewBox.scaleBy(s,x=center.y()) self.centreOn(thisViewBox,x=center.y()) #The following two cases aren't used currently by Lasagna, but may be required in the future. #They haven't been tested yet. [28/07/15] if self.linkedAxis[thisViewBox]['linkY']=='x' and self.linkedAxis[thisViewBox]['linkX']=='y' : thisViewBox.scaleBy(s,x=center.y(),y=center.x()) self.centreOn(thisViewBox,x=center.y(),y=center.x()) if self.linkedAxis[thisViewBox]['linkY']=='y' and self.linkedAxis[thisViewBox]['linkX']=='x' : thisViewBox.scaleBy(s,x=center.x(),y=center.y()) self.centreOn(thisViewBox,x=center.x(),y=center.y())