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.
|
Abstract class providing useful methods to GraphicsObject and GraphicsWidget.
|
||||||
(This is required because we cannot have multiple inheritance with QObject subclasses.)
|
(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):
|
def __init__(self, register=True):
|
||||||
self._viewWidget = None
|
self._viewWidget = None
|
||||||
@ -72,7 +76,15 @@ class GraphicsItem(object):
|
|||||||
if view is None:
|
if view is None:
|
||||||
return None
|
return None
|
||||||
viewportTransform = view.viewportTransform()
|
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):
|
def viewTransform(self):
|
||||||
"""Return the transform that maps from local coordinates to the item's ViewBox coordinates
|
"""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 pixelLength(self, direction):
|
def pixelVectors(self, direction=None):
|
||||||
"""
|
"""Return vectors in local coordinates representing the width and height of a view pixel.
|
||||||
Return the length of one pixel in the direction indicated (in local coordinates)
|
If direction is specified, then return vectors parallel and orthogonal to it.
|
||||||
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()
|
dt = self.deviceTransform()
|
||||||
if dt is None:
|
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)))
|
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
|
||||||
|
orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space
|
||||||
|
|
||||||
try:
|
try:
|
||||||
norm = viewDir.norm()
|
normView = viewDir.norm() ## direction of one pixel orthogonal to line
|
||||||
except ZeroDivisionError:
|
normOrtho = orthoDir.norm()
|
||||||
return None
|
except:
|
||||||
|
raise Exception("Invalid direction %s" %direction)
|
||||||
|
|
||||||
|
|
||||||
dti = dt.inverted()[0]
|
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):
|
def pixelSize(self):
|
||||||
v = self.pixelVectors()
|
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
|
return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5
|
||||||
|
|
||||||
def pixelWidth(self):
|
def pixelWidth(self):
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
from .UIGraphicsItem import UIGraphicsItem
|
from .GraphicsObject import GraphicsObject
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['InfiniteLine']
|
__all__ = ['InfiniteLine']
|
||||||
class InfiniteLine(UIGraphicsItem):
|
class InfiniteLine(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
**Bases:** :class:`UIGraphicsItem <pyqtgraph.UIGraphicsItem>`
|
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||||
|
|
||||||
Displays a line of infinite length.
|
Displays a line of infinite length.
|
||||||
This line may be dragged to indicate a position in data coordinates.
|
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
|
if bounds is None: ## allowed value boundaries for orthogonal lines
|
||||||
self.maxRange = [None, None]
|
self.maxRange = [None, None]
|
||||||
@ -121,7 +121,7 @@ class InfiniteLine(UIGraphicsItem):
|
|||||||
|
|
||||||
if self.p != newPos:
|
if self.p != newPos:
|
||||||
self.p = newPos
|
self.p = newPos
|
||||||
UIGraphicsItem.setPos(self, Point(self.p))
|
GraphicsObject.setPos(self, Point(self.p))
|
||||||
self.update()
|
self.update()
|
||||||
self.sigPositionChanged.emit(self)
|
self.sigPositionChanged.emit(self)
|
||||||
|
|
||||||
@ -161,31 +161,18 @@ class InfiniteLine(UIGraphicsItem):
|
|||||||
#return GraphicsObject.itemChange(self, change, val)
|
#return GraphicsObject.itemChange(self, change, val)
|
||||||
|
|
||||||
def boundingRect(self):
|
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.
|
## add a 4-pixel radius around the line for mouse interaction.
|
||||||
|
|
||||||
#print "line bounds:", self, br
|
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
||||||
dt = self.deviceTransform()
|
if px is None:
|
||||||
if dt is None:
|
px = 0
|
||||||
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
|
|
||||||
|
|
||||||
br.setBottom(-px*4)
|
br.setBottom(-px*4)
|
||||||
br.setTop(px*4)
|
br.setTop(px*4)
|
||||||
return br.normalized()
|
return br.normalized()
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
UIGraphicsItem.paint(self, p, *args)
|
|
||||||
br = self.boundingRect()
|
br = self.boundingRect()
|
||||||
p.setPen(self.currentPen)
|
p.setPen(self.currentPen)
|
||||||
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
||||||
|
Loading…
Reference in New Issue
Block a user