diff --git a/examples/plottingItems.py b/examples/plottingItems.py index e4cb29bb..7815677d 100644 --- a/examples/plottingItems.py +++ b/examples/plottingItems.py @@ -17,14 +17,14 @@ win.resize(1000,600) pg.setConfigOptions(antialias=True) p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100)) -inf1 = pg.InfiniteLine(movable=True, angle=90, label=True, textColor=(200,200,100), textFill=(200,200,200,50)) -inf2 = pg.InfiniteLine(movable=True, angle=0, label=True, pen=(0, 0, 200), bounds = [-2, 2], unit="mm", hoverPen=(0,200,0)) -inf3 = pg.InfiniteLine(movable=True, angle=45) +inf1 = pg.InfiniteLine(movable=True, angle=90, label=False, textColor=(200,200,100), textFill=(200,200,200,50)) +inf2 = pg.InfiniteLine(movable=True, angle=0, label=True, pen=(0, 0, 200), textColor=(200,0,0), bounds = [-2, 2], suffix="mm", hoverPen=(0,200,0)) +#inf3 = pg.InfiniteLine(movable=True, angle=45) inf1.setPos([2,2]) -inf1.setTextLocation([0.25, 0.9]) +##inf1.setTextLocation([0.25, 0.9]) p1.addItem(inf1) p1.addItem(inf2) -p1.addItem(inf3) +#p1.addItem(inf3) lr = pg.LinearRegionItem(values=[0, 10]) p1.addItem(lr) diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index 00b517cf..d645824b 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -1,49 +1,20 @@ from ..Qt import QtGui, QtCore from ..Point import Point -from .UIGraphicsItem import UIGraphicsItem +from .GraphicsObject import GraphicsObject +#from UIGraphicsItem import UIGraphicsItem from .TextItem import TextItem +from .ViewBox import ViewBox from .. import functions as fn import numpy as np import weakref -import math __all__ = ['InfiniteLine'] -def _calcLine(pos, angle, xmin, ymin, xmax, ymax): +class InfiniteLine(GraphicsObject): """ - Evaluate the location of the points that delimitates a line into a viewbox - described by x and y ranges. Depending on the angle value, pos can be a - float (if angle=0 and 90) or a list of float (x and y coordinates). - Could be possible to beautify this piece of code. - New in verson 0.9.11 - """ - if angle == 0: - x1, y1, x2, y2 = xmin, pos, xmax, pos - elif angle == 90: - x1, y1, x2, y2 = pos, ymin, pos, ymax - else: - x0, y0 = pos - tana = math.tan(angle*math.pi/180) - y1 = tana*(xmin-x0) + y0 - y2 = tana*(xmax-x0) + y0 - if angle > 0: - y1 = max(y1, ymin) - y2 = min(y2, ymax) - else: - y1 = min(y1, ymax) - y2 = max(y2, ymin) - x1 = (y1-y0)/tana + x0 - x2 = (y2-y0)/tana + x0 - p1 = Point(x1, y1) - p2 = Point(x2, y2) - return p1, p2 - - -class InfiniteLine(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` + **Bases:** :class:`GraphicsObject ` Displays a line of infinite length. This line may be dragged to indicate a position in data coordinates. @@ -54,10 +25,6 @@ class InfiniteLine(UIGraphicsItem): sigPositionChangeFinished(self) sigPositionChanged(self) =============================== =================================================== - - Major changes have been performed in this class since version 0.9.11. The - number of methods in the public API has been increased, but the already - existing methods can be used in the same way. """ sigDragged = QtCore.Signal(object) @@ -66,8 +33,8 @@ class InfiniteLine(UIGraphicsItem): def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None, hoverPen=None, label=False, textColor=None, textFill=None, - textLocation=0.05, textShift=0.5, textFormat="{:.3f}", - unit=None, name=None): + textLocation=[0.05,0.5], textFormat="{:.3f}", + suffix=None, name='InfiniteLine'): """ =============== ================================================================== **Arguments:** @@ -87,65 +54,63 @@ class InfiniteLine(UIGraphicsItem): location in data coordinates textColor color of the label. Can be any argument fn.mkColor can understand. textFill A brush to use when filling within the border of the text. - textLocation A float [0-1] that defines the location of the text. - textShift A float [0-1] that defines when the text shifts from one side to - another. + textLocation list where list[0] defines the location of the text (if + vertical, a 0 value means that the textItem is on the bottom + axis, and a 1 value means that thet TextItem is on the top + axis, same thing if horizontal) and list[1] defines when the + text shifts from one side to the other side of the line. textFormat Any new python 3 str.format() format. - unit If not None, corresponds to the unit to show next to the label - name If not None, corresponds to the name of the object + suffix If not None, corresponds to the unit to show next to the label + name name of the item =============== ================================================================== """ - UIGraphicsItem.__init__(self) + GraphicsObject.__init__(self) if bounds is None: ## allowed value boundaries for orthogonal lines self.maxRange = [None, None] else: self.maxRange = bounds self.moving = False + self.setMovable(movable) self.mouseHovering = False + self.p = [0, 0] + self.setAngle(angle) - self.angle = ((angle+45) % 180) - 45 if textColor is None: - textColor = (200, 200, 200) + textColor = (200, 200, 100) self.textColor = textColor - self.location = textLocation - self.shift = textShift - self.label = label - self.format = textFormat - self.unit = unit - self._name = name + self.textFill = textFill + self.textLocation = textLocation + self.suffix = suffix + + if (self.angle == 0 or self.angle == 90) and label: + self.textItem = TextItem(fill=textFill) + self.textItem.setParentItem(self) + else: + self.textItem = None self.anchorLeft = (1., 0.5) self.anchorRight = (0., 0.5) self.anchorUp = (0.5, 1.) self.anchorDown = (0.5, 0.) - self.text = TextItem(fill=textFill) - self.text.setParentItem(self) # important - self.p = [0, 0] + + if pos is None: + pos = Point(0,0) + self.setPos(pos) if pen is None: pen = (200, 200, 100) - self.setPen(pen) - if hoverPen is None: self.setHoverPen(color=(255,0,0), width=self.pen.width()) else: self.setHoverPen(hoverPen) self.currentPen = self.pen - self.setMovable(movable) - - if pos is None: - pos = Point(0,0) - self.setPos(pos) - - if (self.angle == 0 or self.angle == 90) and self.label: - self.text.show() - else: - self.text.hide() + self.format = textFormat + self._name = name def setMovable(self, m): """Set whether the line is movable by the user.""" @@ -187,12 +152,8 @@ class InfiniteLine(UIGraphicsItem): not vertical or horizontal. """ self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 - # self.resetTransform() # no longer needed since version 0.9.11 - # self.rotate(self.angle) # no longer needed since version 0.9.11 - if (self.angle == 0 or self.angle == 90) and self.label: - self.text.show() - else: - self.text.hide() + self.resetTransform() + self.rotate(self.angle) self.update() def setPos(self, pos): @@ -223,10 +184,47 @@ class InfiniteLine(UIGraphicsItem): if self.p != newPos: self.p = newPos - # UIGraphicsItem.setPos(self, Point(self.p)) # thanks Sylvain! + GraphicsObject.setPos(self, Point(self.p)) + + if self.textItem is not None and self.getViewBox() is not None and isinstance(self.getViewBox(), ViewBox): + self.updateTextPosition() + self.update() self.sigPositionChanged.emit(self) + def updateTextPosition(self): + """ + Update the location of the textItem. Called only if a textItem is + requested and if the item has already been added to a PlotItem. + """ + rangeX, rangeY = self.getViewBox().viewRange() + xmin, xmax = rangeX + ymin, ymax = rangeY + if self.angle == 90: # vertical line + diffMin = self.value()-xmin + limInf = self.textLocation[1]*(xmax-xmin) + ypos = ymin+self.textLocation[0]*(ymax-ymin) + if diffMin < limInf: + self.textItem.anchor = Point(self.anchorRight) + else: + self.textItem.anchor = Point(self.anchorLeft) + fmt = " x = " + self.format + if self.suffix is not None: + fmt = fmt + self.suffix + self.textItem.setText(fmt.format(self.value()), color=self.textColor) + elif self.angle == 0: # horizontal line + diffMin = self.value()-ymin + limInf = self.textLocation[1]*(ymax-ymin) + xpos = xmin+self.textLocation[0]*(xmax-xmin) + if diffMin < limInf: + self.textItem.anchor = Point(self.anchorUp) + else: + self.textItem.anchor = Point(self.anchorDown) + fmt = " y = " + self.format + if self.suffix is not None: + fmt = fmt + self.suffix + self.textItem.setText(fmt.format(self.value()), color=self.textColor) + def getXPos(self): return self.p[0] @@ -263,54 +261,22 @@ class InfiniteLine(UIGraphicsItem): #return GraphicsObject.itemChange(self, change, val) def boundingRect(self): - br = UIGraphicsItem.boundingRect(self) # directly in viewBox coordinates - # we need to limit the boundingRect to the appropriate value. - val = self.value() - if self.angle == 0: # horizontal line - self._p1, self._p2 = _calcLine(val, 0, *br.getCoords()) - px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line - if px is None: - px = 0 - w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px - o1, o2 = _calcLine(val-w, 0, *br.getCoords()) - o3, o4 = _calcLine(val+w, 0, *br.getCoords()) - elif self.angle == 90: # vertical line - self._p1, self._p2 = _calcLine(val, 90, *br.getCoords()) - px = self.pixelLength(direction=Point(0,1), ortho=True) ## get pixel length orthogonal to the line - if px is None: - px = 0 - w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px - o1, o2 = _calcLine(val-w, 90, *br.getCoords()) - o3, o4 = _calcLine(val+w, 90, *br.getCoords()) - else: # oblique line - self._p1, self._p2 = _calcLine(val, self.angle, *br.getCoords()) - pxy = self.pixelLength(direction=Point(0,1), ortho=True) - if pxy is None: - pxy = 0 - wy = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * pxy - pxx = self.pixelLength(direction=Point(1,0), ortho=True) - if pxx is None: - pxx = 0 - wx = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * pxx - o1, o2 = _calcLine([val[0]-wy, val[1]-wx], self.angle, *br.getCoords()) - o3, o4 = _calcLine([val[0]+wy, val[1]+wx], self.angle, *br.getCoords()) - self._polygon = QtGui.QPolygonF([o1, o2, o4, o3]) - br = self._polygon.boundingRect() + #br = UIGraphicsItem.boundingRect(self) + br = self.viewRect() + ## add a 4-pixel radius around the line for mouse interaction. + + px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line + if px is None: + px = 0 + w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px + br.setBottom(-w) + br.setTop(w) return br.normalized() - - def shape(self): - # returns a QPainterPath. Needed when the item is non rectangular if - # accurate mouse click detection is required. - # New in version 0.9.11 - qpp = QtGui.QPainterPath() - qpp.addPolygon(self._polygon) - return qpp - def paint(self, p, *args): br = self.boundingRect() p.setPen(self.currentPen) - p.drawLine(self._p1, self._p2) + p.drawLine(Point(br.right(), 0), Point(br.left(), 0)) def dataBounds(self, axis, frac=1.0, orthoRange=None): if axis == 0: @@ -322,15 +288,14 @@ class InfiniteLine(UIGraphicsItem): if self.movable and ev.button() == QtCore.Qt.LeftButton: if ev.isStart(): self.moving = True - self.cursorOffset = self.value() - ev.buttonDownPos() - self.startPosition = self.value() + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() ev.accept() if not self.moving: return - self.setPos(self.cursorOffset + ev.pos()) - self.prepareGeometryChange() # new in version 0.9.11 + self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) self.sigDragged.emit(self) if ev.isFinish(): self.moving = False @@ -361,39 +326,14 @@ class InfiniteLine(UIGraphicsItem): self.currentPen = self.pen self.update() - def update(self): - # new in version 0.9.11 - UIGraphicsItem.update(self) - br = UIGraphicsItem.boundingRect(self) # directly in viewBox coordinates - xmin, ymin, xmax, ymax = br.getCoords() - if self.angle == 90: # vertical line - diffX = xmax-xmin - diffMin = self.value()-xmin - limInf = self.shift*diffX - ypos = ymin+self.location*(ymax-ymin) - if diffMin < limInf: - self.text.anchor = Point(self.anchorRight) - else: - self.text.anchor = Point(self.anchorLeft) - fmt = " x = " + self.format - if self.unit is not None: - fmt = fmt + self.unit - self.text.setText(fmt.format(self.value()), color=self.textColor) - self.text.setPos(self.value(), ypos) - elif self.angle == 0: # horizontal line - diffY = ymax-ymin - diffMin = self.value()-ymin - limInf = self.shift*(ymax-ymin) - xpos = xmin+self.location*(xmax-xmin) - if diffMin < limInf: - self.text.anchor = Point(self.anchorUp) - else: - self.text.anchor = Point(self.anchorDown) - fmt = " y = " + self.format - if self.unit is not None: - fmt = fmt + self.unit - self.text.setText(fmt.format(self.value()), color=self.textColor) - self.text.setPos(xpos, self.value()) + def viewTransformChanged(self): + """ + Called whenever the transformation matrix of the view has changed. + (eg, the view range has changed or the view was resized) + """ + if self.getViewBox() is not None and isinstance(self.getViewBox(), ViewBox) and self.textItem is not None: + self.updateTextPosition() + #GraphicsObject.viewTransformChanged(self) def showLabel(self, state): """ @@ -406,54 +346,24 @@ class InfiniteLine(UIGraphicsItem): ============== ============================================== """ if state: - self.text.show() + self.textItem = TextItem(fill=self.textFill) + self.textItem.setParentItem(self) + self.viewTransformChanged() else: - self.text.hide() - self.update() + self.textItem = None - def setTextLocation(self, param): + + def setTextLocation(self, loc): """ - Set the location of the label. param is a list of two values. - param[0] defines the location of the label along the axis and - param[1] defines the shift value (defines the condition where the - label shifts from one side of the line to the other one). - New in version 0.9.11 - ============== ============================================== - **Arguments:** - param list of parameters. - ============== ============================================== + Set the parameters that defines the location of the textItem with respect + to a specific axis. If the line is vertical, the location is based on the + normalized range of the yaxis. Otherwise, it is based on the normalized + range of the xaxis. + loc[0] defines the location of the text along the infiniteLine + loc[1] defines the location when the label shifts from one side of then + infiniteLine to the other. """ - if len(param) != 2: # check that the input data are correct - return - self.location = np.clip(param[0], 0, 1) - self.shift = np.clip(param[1], 0, 1) - self.update() - - def setFormat(self, format): - """ - Set the format of the label used to indicate the location of the line. - - - ============== ============================================== - **Arguments:** - format Any format compatible with the new python - str.format() format style. - ============== ============================================== - """ - self.format = format - self.update() - - def setUnit(self, unit): - """ - Set the unit of the label used to indicate the location of the line. - - - ============== ============================================== - **Arguments:** - unit Any string. - ============== ============================================== - """ - self.unit = unit + self.textLocation = [np.clip(loc[0], 0, 1), np.clip(loc[1], 0, 1)] self.update() def setName(self, name): diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/pyqtgraph/graphicsItems/LinearRegionItem.py index 96b27720..e139190b 100644 --- a/pyqtgraph/graphicsItems/LinearRegionItem.py +++ b/pyqtgraph/graphicsItems/LinearRegionItem.py @@ -9,10 +9,10 @@ __all__ = ['LinearRegionItem'] class LinearRegionItem(UIGraphicsItem): """ **Bases:** :class:`UIGraphicsItem ` - + Used for marking a horizontal or vertical region in plots. The region can be dragged and is bounded by lines which can be dragged individually. - + =============================== ============================================================================= **Signals:** sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) @@ -21,15 +21,15 @@ class LinearRegionItem(UIGraphicsItem): and when the region is changed programatically. =============================== ============================================================================= """ - + sigRegionChangeFinished = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object) Vertical = 0 Horizontal = 1 - + def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None): """Create a new LinearRegionItem. - + ============== ===================================================================== **Arguments:** values A list of the positions of the lines in the region. These are not @@ -44,7 +44,7 @@ class LinearRegionItem(UIGraphicsItem): bounds Optional [min, max] bounding values for the region ============== ===================================================================== """ - + UIGraphicsItem.__init__(self) if orientation is None: orientation = LinearRegionItem.Vertical @@ -53,30 +53,30 @@ class LinearRegionItem(UIGraphicsItem): self.blockLineSignal = False self.moving = False self.mouseHovering = False - + if orientation == LinearRegionItem.Horizontal: self.lines = [ - InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), InfiniteLine(QtCore.QPointF(0, values[1]), 0, movable=movable, bounds=bounds)] elif orientation == LinearRegionItem.Vertical: self.lines = [ - InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), InfiniteLine(QtCore.QPointF(values[0], 0), 90, movable=movable, bounds=bounds)] else: raise Exception('Orientation must be one of LinearRegionItem.Vertical or LinearRegionItem.Horizontal') - - + + for l in self.lines: l.setParentItem(self) l.sigPositionChangeFinished.connect(self.lineMoveFinished) l.sigPositionChanged.connect(self.lineMoved) - + if brush is None: brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) self.setBrush(brush) - + self.setMovable(movable) - + def getRegion(self): """Return the values at the edges of the region.""" #if self.orientation[0] == 'h': @@ -88,7 +88,7 @@ class LinearRegionItem(UIGraphicsItem): def setRegion(self, rgn): """Set the values for the edges of the region. - + ============== ============================================== **Arguments:** rgn A list or tuple of the lower and upper values. @@ -114,14 +114,14 @@ class LinearRegionItem(UIGraphicsItem): def setBounds(self, bounds): """Optional [min, max] bounding values for the region. To have no bounds on the region use [None, None]. - Does not affect the current position of the region unless it is outside the new bounds. - See :func:`setRegion ` to set the position + Does not affect the current position of the region unless it is outside the new bounds. + See :func:`setRegion ` to set the position of the region.""" for l in self.lines: l.setBounds(bounds) - + def setMovable(self, m): - """Set lines to be movable by the user, or not. If lines are movable, they will + """Set lines to be movable by the user, or not. If lines are movable, they will also accept HoverEvents.""" for l in self.lines: l.setMovable(m) @@ -138,7 +138,7 @@ class LinearRegionItem(UIGraphicsItem): br.setTop(rng[0]) br.setBottom(rng[1]) return br.normalized() - + def paint(self, p, *args): profiler = debug.Profiler() UIGraphicsItem.paint(self, p, *args) @@ -158,12 +158,12 @@ class LinearRegionItem(UIGraphicsItem): self.prepareGeometryChange() #self.emit(QtCore.SIGNAL('regionChanged'), self) self.sigRegionChanged.emit(self) - + def lineMoveFinished(self): #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) self.sigRegionChangeFinished.emit(self) - - + + #def updateBounds(self): #vb = self.view().viewRect() #vals = [self.lines[0].value(), self.lines[1].value()] @@ -176,7 +176,7 @@ class LinearRegionItem(UIGraphicsItem): #if vb != self.bounds: #self.bounds = vb #self.rect.setRect(vb) - + #def mousePressEvent(self, ev): #if not self.movable: #ev.ignore() @@ -188,11 +188,11 @@ class LinearRegionItem(UIGraphicsItem): ##self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) ##else: ##ev.ignore() - + #def mouseReleaseEvent(self, ev): #for l in self.lines: #l.mouseReleaseEvent(ev) - + #def mouseMoveEvent(self, ev): ##print "move", ev.pos() #if not self.movable: @@ -208,16 +208,16 @@ class LinearRegionItem(UIGraphicsItem): if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: return ev.accept() - + if ev.isStart(): bdp = ev.buttonDownPos() - self.cursorOffsets = [l.value() - bdp for l in self.lines] - self.startPositions = [l.value() for l in self.lines] + self.cursorOffsets = [l.pos() - bdp for l in self.lines] + self.startPositions = [l.pos() for l in self.lines] self.moving = True - + if not self.moving: return - + #delta = ev.pos() - ev.lastPos() self.lines[0].blockSignals(True) # only want to update once for i, l in enumerate(self.lines): @@ -226,13 +226,13 @@ class LinearRegionItem(UIGraphicsItem): #l.mouseDragEvent(ev) self.lines[0].blockSignals(False) self.prepareGeometryChange() - + if ev.isFinish(): self.moving = False self.sigRegionChangeFinished.emit(self) else: self.sigRegionChanged.emit(self) - + def mouseClickEvent(self, ev): if self.moving and ev.button() == QtCore.Qt.RightButton: ev.accept() @@ -248,7 +248,7 @@ class LinearRegionItem(UIGraphicsItem): self.setMouseHover(True) else: self.setMouseHover(False) - + def setMouseHover(self, hover): ## Inform the item that the mouse is(not) hovering over it if self.mouseHovering == hover: @@ -276,14 +276,15 @@ class LinearRegionItem(UIGraphicsItem): #print "rgn hover leave" #ev.ignore() #self.updateHoverBrush(False) - + #def updateHoverBrush(self, hover=None): #if hover is None: #scene = self.scene() #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) - + #if hover: #self.currentBrush = fn.mkBrush(255, 0,0,100) #else: #self.currentBrush = self.brush #self.update() +