Search code examples
pythondrag-and-droppyqtpyqt4pyqtgraph

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.


Solution

  • 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