Changed the way GraphicsItem.pixelVectors and pixelLength work.
The values returned are more useful now, but this introduces a minor API change.
This commit is contained in:
parent
5f94cebdaf
commit
724debf2d4
@ -9,6 +9,10 @@ class GraphicsItem(object):
|
||||
|
||||
Abstract class providing useful methods to GraphicsObject and GraphicsWidget.
|
||||
(This is required because we cannot have multiple inheritance with QObject subclasses.)
|
||||
|
||||
A note about Qt's GraphicsView framework:
|
||||
|
||||
The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task.
|
||||
"""
|
||||
def __init__(self, register=True):
|
||||
self._viewWidget = None
|
||||
@ -31,10 +35,10 @@ class GraphicsItem(object):
|
||||
return None
|
||||
self._viewWidget = weakref.ref(self.scene().views()[0])
|
||||
return self._viewWidget()
|
||||
|
||||
|
||||
def forgetViewWidget(self):
|
||||
self._viewWidget = None
|
||||
|
||||
|
||||
def getViewBox(self):
|
||||
"""
|
||||
Return the first ViewBox or GraphicsView which bounds this item's visible space.
|
||||
@ -72,7 +76,15 @@ class GraphicsItem(object):
|
||||
if view is None:
|
||||
return None
|
||||
viewportTransform = view.viewportTransform()
|
||||
return QtGui.QGraphicsObject.deviceTransform(self, viewportTransform)
|
||||
dt = QtGui.QGraphicsObject.deviceTransform(self, viewportTransform)
|
||||
|
||||
#xmag = abs(dt.m11())+abs(dt.m12())
|
||||
#ymag = abs(dt.m21())+abs(dt.m22())
|
||||
#if xmag * ymag == 0:
|
||||
if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed
|
||||
return None
|
||||
else:
|
||||
return dt
|
||||
|
||||
def viewTransform(self):
|
||||
"""Return the transform that maps from local coordinates to the item's ViewBox coordinates
|
||||
@ -123,35 +135,59 @@ class GraphicsItem(object):
|
||||
|
||||
|
||||
|
||||
def pixelVectors(self):
|
||||
"""Return vectors in local coordinates representing the width and height of a view pixel."""
|
||||
vt = self.deviceTransform()
|
||||
if vt is None:
|
||||
return None
|
||||
vt = vt.inverted()[0]
|
||||
orig = vt.map(QtCore.QPointF(0, 0))
|
||||
return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig
|
||||
|
||||
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.
|
||||
|
||||
def pixelLength(self, direction):
|
||||
"""
|
||||
Return the length of one pixel in the direction indicated (in local coordinates)
|
||||
If the result would be infinite (this happens if the device transform is not properly configured yet),
|
||||
then return None instead.
|
||||
"""
|
||||
Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed)."""
|
||||
|
||||
dt = self.deviceTransform()
|
||||
if dt is None:
|
||||
return None
|
||||
return None, None
|
||||
|
||||
if direction is None:
|
||||
direction = Point(1, 0)
|
||||
|
||||
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
|
||||
try:
|
||||
norm = viewDir.norm()
|
||||
except ZeroDivisionError:
|
||||
return None
|
||||
orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space
|
||||
|
||||
try:
|
||||
normView = viewDir.norm() ## direction of one pixel orthogonal to line
|
||||
normOrtho = orthoDir.norm()
|
||||
except:
|
||||
raise Exception("Invalid direction %s" %direction)
|
||||
|
||||
|
||||
dti = dt.inverted()[0]
|
||||
return Point(dti.map(norm)-dti.map(Point(0,0))).length()
|
||||
return Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0)))
|
||||
|
||||
#vt = self.deviceTransform()
|
||||
#if vt is None:
|
||||
#return None
|
||||
#vt = vt.inverted()[0]
|
||||
#orig = vt.map(QtCore.QPointF(0, 0))
|
||||
#return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig
|
||||
|
||||
def pixelLength(self, direction, ortho=False):
|
||||
"""Return the length of one pixel in the direction indicated (in local coordinates)
|
||||
If ortho=True, then return the length of one pixel orthogonal to the direction indicated.
|
||||
|
||||
Return None if pixel size is not yet defined (usually because the item has not yet been displayed).
|
||||
"""
|
||||
normV, orthoV = self.pixelVectors(direction)
|
||||
if normV == None or orthoV == None:
|
||||
return None
|
||||
if ortho:
|
||||
return orthoV.length()
|
||||
return normV.length()
|
||||
|
||||
|
||||
|
||||
def pixelSize(self):
|
||||
v = self.pixelVectors()
|
||||
if v == (None, None):
|
||||
return None, None
|
||||
return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5
|
||||
|
||||
def pixelWidth(self):
|
||||
|
@ -1,15 +1,15 @@
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from pyqtgraph.Point import Point
|
||||
from .UIGraphicsItem import UIGraphicsItem
|
||||
from .GraphicsObject import GraphicsObject
|
||||
import pyqtgraph.functions as fn
|
||||
import numpy as np
|
||||
import weakref
|
||||
|
||||
|
||||
__all__ = ['InfiniteLine']
|
||||
class InfiniteLine(UIGraphicsItem):
|
||||
class InfiniteLine(GraphicsObject):
|
||||
"""
|
||||
**Bases:** :class:`UIGraphicsItem <pyqtgraph.UIGraphicsItem>`
|
||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||
|
||||
Displays a line of infinite length.
|
||||
This line may be dragged to indicate a position in data coordinates.
|
||||
@ -42,7 +42,7 @@ class InfiniteLine(UIGraphicsItem):
|
||||
============= ==================================================================
|
||||
"""
|
||||
|
||||
UIGraphicsItem.__init__(self)
|
||||
GraphicsObject.__init__(self)
|
||||
|
||||
if bounds is None: ## allowed value boundaries for orthogonal lines
|
||||
self.maxRange = [None, None]
|
||||
@ -121,7 +121,7 @@ class InfiniteLine(UIGraphicsItem):
|
||||
|
||||
if self.p != newPos:
|
||||
self.p = newPos
|
||||
UIGraphicsItem.setPos(self, Point(self.p))
|
||||
GraphicsObject.setPos(self, Point(self.p))
|
||||
self.update()
|
||||
self.sigPositionChanged.emit(self)
|
||||
|
||||
@ -161,31 +161,18 @@ class InfiniteLine(UIGraphicsItem):
|
||||
#return GraphicsObject.itemChange(self, change, val)
|
||||
|
||||
def boundingRect(self):
|
||||
br = UIGraphicsItem.boundingRect(self)
|
||||
|
||||
#br = UIGraphicsItem.boundingRect(self)
|
||||
br = self.viewRect()
|
||||
## add a 4-pixel radius around the line for mouse interaction.
|
||||
|
||||
#print "line bounds:", self, br
|
||||
dt = self.deviceTransform()
|
||||
if dt is None:
|
||||
return QtCore.QRectF()
|
||||
lineDir = Point(dt.map(Point(1, 0)) - dt.map(Point(0,0))) ## direction of line in pixel-space
|
||||
orthoDir = Point(lineDir[1], -lineDir[0]) ## orthogonal to line in pixel-space
|
||||
try:
|
||||
norm = orthoDir.norm() ## direction of one pixel orthogonal to line
|
||||
except ZeroDivisionError:
|
||||
return br
|
||||
|
||||
dti = dt.inverted()[0]
|
||||
px = Point(dti.map(norm)-dti.map(Point(0,0))) ## orthogonal pixel mapped back to item coords
|
||||
px = px[1] ## project to y-direction
|
||||
|
||||
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
||||
if px is None:
|
||||
px = 0
|
||||
br.setBottom(-px*4)
|
||||
br.setTop(px*4)
|
||||
return br.normalized()
|
||||
|
||||
def paint(self, p, *args):
|
||||
UIGraphicsItem.paint(self, p, *args)
|
||||
br = self.boundingRect()
|
||||
p.setPen(self.currentPen)
|
||||
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
||||
|
Loading…
Reference in New Issue
Block a user