SunnyIsaLearner SunnyIsaLearner - 1 month ago 11
Python Question

PyQt draggable line with multiple break points

I have a application very similar to the following question:Draw half infinite lines?

I would like to have a infinite line with multiple thresholds.

enter image description here

The solution provided in the question is a great starting point: https://stackoverflow.com/a/37836348/7163293

I attempted to make the lines movable by modifying the

movable
attribute in
__init__
and add a
setMovable
method just as the source code in source:

http://www.pyqtgraph.org/documentation/_modules/pyqtgraph/graphicsItems/InfiniteLine.html#InfiniteLine

from pyqtgraph.Qt import QtGui
import numpy as np
import pyqtgraph as pg

class InfiniteLineWithBreak(pg.GraphicsObject):

def __init__(self, changeX, levelsY, pen=None):
pg.GraphicsObject.__init__(self)

self.changeX = changeX
self.levelsY = levelsY

self.maxRange = [None, None]
self.moving = False
self.movable = True
self.setMovable(self.movable)
self.mouseHovering = False

pen = (200, 200, 100)
self.setPen(pen)
self.setHoverPen(color=(255,0,0), width=self.pen.width())
self.currentPen = self.pen

def setMovable(self, m):
"""Set whether the line is movable by the user."""
self.movable = m
self.setAcceptHoverEvents(m)


def setBounds(self, bounds):
self.maxRange = bounds
self.setValue(self.value())

def setPen(self, *args, **kwargs):
self.pen = pg.fn.mkPen(*args, **kwargs)
if not self.mouseHovering:
self.currentPen = self.pen
self.update()

def setHoverPen(self, *args, **kwargs):
self.hoverPen = pg.fn.mkPen(*args, **kwargs)
if self.mouseHovering:
self.currentPen = self.hoverPen
self.update()

def boundingRect(self):
br = self.viewRect()
return br.normalized()

def paint(self, p, *args):
br = self.boundingRect()
p.setPen(self.currentPen)
# three lines (left border to change point, change point vertical, change point to right)
p.drawLine(pg.Point(br.left(), self.levelsY[0]), pg.Point(self.changeX, self.levelsY[0]))
p.drawLine(pg.Point(self.changeX, self.levelsY[0]), pg.Point(self.changeX, self.levelsY[1]))
p.drawLine(pg.Point(self.changeX, self.levelsY[1]), pg.Point(br.right(), self.levelsY[1]))

def dataBounds(self, axis, frac=1.0, orthoRange=None):
if axis == 0:
return None ## x axis should never be auto-scaled
else:
return (0,0)

def setMouseHover(self, hover):
pass

app = QtGui.QApplication([])
w = pg.GraphicsWindow()
w.resize(1000, 600)
v = w.addPlot(y=np.random.normal(size=100))
v.addItem(InfiniteLineWithBreak(changeX=50, levelsY=(-1, 1)))
app.exec_()


However, the line is still not movable after the modifications.So I am kind of stuck here. Would someone be able to provide some pointers?

Also, ideally, the line on the applications should be movable by segments. So when the user drag a line, only the portion in between break points are moving. So ideally I would like to have something like:

Draggable line with draggable points

in my application. Ideally it would look something like

enter image description here

with the threshold point level (TH_Px_L1) draggable but not the timing (TH_Px_T1), so the points can only move vertically.

If someone can also help on the second item and provide some pointers or solution that will be very helpful.

Answer Source

Based on this example from docs.

A scatter type graph is similar to one where graphs are drawn, but where the connecting lines are between the i-point point and the i + 1-point point they are connected. Then we limit the movement only to the vertical axis since it is a requirement of the author.

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui

pg.setConfigOptions(antialias=True)

w = pg.GraphicsWindow()
w.setWindowTitle('Draggable')


class Graph(pg.GraphItem):
    def __init__(self):
        self.dragPoint = None
        self.dragOffset = None
        pg.GraphItem.__init__(self)

    def setData(self, **kwds):
        self.data = kwds
        if 'pos' in self.data:
            npts = self.data['pos'].shape[0]
            self.data['adj'] = np.column_stack((np.arange(0, npts-1), np.arange(1, npts)))
            self.data['data'] = np.empty(npts, dtype=[('index', int)])
            self.data['data']['index'] = np.arange(npts)
        self.updateGraph()

    def updateGraph(self):
        pg.GraphItem.setData(self, **self.data)

    def mouseDragEvent(self, ev):
        if ev.button() != QtCore.Qt.LeftButton:
            ev.ignore()
            return

        if ev.isStart():
            pos = ev.buttonDownPos()
            pts = self.scatter.pointsAt(pos)
            if len(pts) == 0:
                ev.ignore()
                return
            self.dragPoint = pts[0]
            ind = pts[0].data()[0]
            self.dragOffset = self.data['pos'][ind][1] - pos[1]
        elif ev.isFinish():
            self.dragPoint = None
            return
        else:
            if self.dragPoint is None:
                ev.ignore()
                return

        ind = self.dragPoint.data()[0]
        self.data['pos'][ind][1] = ev.pos()[1] + self.dragOffset
        self.updateGraph()
        ev.accept()


g = Graph()
v = w.addPlot()
v.addItem(g)

x = np.linspace(1, 100, 40)
pos = np.column_stack((x, np.sin(x)))

g.setData(pos=pos, size=10, pxMode=True)

if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

enter image description here