Search code examples
pythonpyqtpyqtgraph

How to move the X-axis of a pyqtgraph back and fourth when a draggable line is nearing the X-range endings?


I have a pyqtgraph plotwidget displaying a curve and I am only showing a limited range of the X-axis due to the large data array. I would like to use a draggable line to push the X-axis back and fourth when the line is dragged near and pass the endings of the X-range.

This I have done. However, when in full screen the drag event stops updating when the mouse cant move further to the right or left of the screen.

How can I continue updating the X-Axis even when the mouse is static but near the endings of the X-Range?

Sample code:

# Import packages
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
import numpy as np
import sys
import pyqtgraph as pg

class MainWindow(QWidget):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.verticalLayout = QVBoxLayout(self)

        # Add graph plot
        self.graph = pg.PlotWidget()
        self.verticalLayout.addWidget(self.graph)

        # Set view range to 200 as default
        self.startX = 0
        self.endX = 200

        self.graph.setMouseEnabled(x=False, y=False)  # disable mouse events in graph
        self.graph.hideButtons()
        self.graph.setXRange(self.startX, self.endX, padding=0) # Set view range to 200 as default
        self.curve = self.graph.plot(pen="w") # Add line to plot

        data = np.random.random(size=10000)

        self.curve.setData(data)

        # Add line to graph plot
        self.vLine = pg.InfiniteLine(movable=True, angle=90, pen=[75, 82, 159, 200])
        self.graph.addItem(self.vLine)
        self.vLine.setPos(100)
        self.graph.scene().sigMouseClicked.connect(self.moveLine)
        self.vLine.sigDragged.connect(self.pushLine)

        self.setLayout(self.verticalLayout)

    def pushLine(self):
        pos = self.vLine.getPos()
        axX = self.graph.getAxis('bottom')

        while pos[0] <= axX.range[0]:
            self.graph.setXRange(axX.range[0]-1, axX.range[1]-1, padding=0)

        while pos[0] >= axX.range[1]:
            self.graph.setXRange(axX.range[0]+1, axX.range[1]+1, padding=0)

    def moveLine(self, mouse_event):
        vb = self.graph.getViewBox()
        view_coords = vb.mapSceneToView(mouse_event.scenePos())
        view_x = view_coords.x()
        self.vLine.setPos(view_x)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

Solution

  • I figured it out. I had to add a QTimer that would start when i dragged the line and stop again when releasing. This created a continously updating signal while dragging. See below to find the code:

    # Import packages
    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
    from PyQt5.QtCore import QTimer
    import numpy as np
    import sys
    import pyqtgraph as pg
    
    class MainWindow(QWidget):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            self.verticalLayout = QVBoxLayout(self)
    
            # Add graph plot
            self.graph = pg.PlotWidget()
            self.verticalLayout.addWidget(self.graph)
    
            # Set view range to 200 as default
            self.startX = 0
            self.endX = 200
    
            self.graph.setMouseEnabled(x=False, y=False)  # disable mouse events in graph
            self.graph.hideButtons()
            self.graph.setXRange(self.startX, self.endX, padding=0) # Set view range to 200 as default
            self.curve = self.graph.plot(pen="w") # Add line to plot
    
            data = np.random.random(size=10000)
    
            self.curve.setData(data)
    
            # Add line to graph plot
            self.vLine = pg.InfiniteLine(movable=True, angle=90, pen=[75, 82, 159, 200])
            self.graph.addItem(self.vLine)
            self.vLine.setPos(100)
            self.graph.scene().sigMouseClicked.connect(self.moveLine)
    
            self.setLayout(self.verticalLayout)
    
            self.playTimer = QTimer()
            self.proxy = pg.SignalProxy(self.graph.scene().sigMouseMoved, rateLimit=30, slot=self.OnMouseMove)
            self.graph.scene().sigMouseClicked.connect(self.release)
            self.playTimer.timeout.connect(self.release)
    
        def release(self):
            pos = self.vLine.getPos()
            axX = self.graph.getAxis('bottom')
    
            if pos[0] <= axX.range[0]:
                self.graph.setXRange(axX.range[0]-1, axX.range[1]-1, padding=0)
                self.vLine.setPos(axX.range[0])
    
            if pos[0] >= axX.range[1]:
                self.graph.setXRange(axX.range[0]+1, axX.range[1]+1, padding=0)
                self.vLine.setPos(axX.range[1])
    
            if not self.graph.scene().clickEvents:  # Drag function
                self.playTimer.stop()
    
        def OnMouseMove(self):
            if not self.playTimer.isActive() and self.graph.scene().clickEvents:
                self.playTimer.start(10)  # After a drag release, this is the "wait" time before self.release is called.
    
        def moveLine(self, mouse_event):
            vb = self.graph.getViewBox()
            view_coords = vb.mapSceneToView(mouse_event.scenePos())
            view_x = view_coords.x()
            self.vLine.setPos(view_x)
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex = MainWindow()
        ex.show()
        sys.exit(app.exec_())