Markers: sample code for implementation

1,481 views
Skip to first unread message

ivan....@gmail.com

unread,
May 19, 2016, 12:53:57 PM5/19/16
to pyqtgraph

Below is some code I created for adding "markers" to a plot. By markers I mean a widget you can add to a plot to show the x,y coordinates of a point on a graph to allow a user to place a bookmark on a specific point on a graph. These markers were created using the ROI widget.

Some of the features:
  1. Markers are created by clicking the hairline cursor to a specific position on the graph and pressing Return.
  2. Any number of markers can be added to a plot.
  3. Markers can be dragged by clicking on the marker ROI or its handle and will follow the curve as they are dragged. They cannot be dragged outside of the curve.
  4. The marker has an attached TextItem that displays the x,y coordinate of the marker's position.
  5. When markers overlap, they can be brought to the foreground by clicking on the marker's ROI. Dragging a marker or its handle also brings it to the foreground.
  6. When the graph is zoomed in or out, the marker size changes dynamically.
  7. Right clicking on the marker's ROI allows the marker to be removed.

I would like to add two things: (1) clicking on the TextItem would bring the marker to the foreground (however there doesn't seem to be a way of intercepting mouse clicks on a TextItem); (2) clicking on the ROI handle would also bring the marker to the foreground and right clicking on the handle would bring up the ROI context sensitive menu.





from PyQt4 import QtCore
from PyQt4.QtGui import QApplication
import numpy as np
import pyqtgraph as pg

class MarkerDemo:
    def __init__(self):
        self.window = pg.GraphicsWindow(size=(800,800), border=True)
        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.label = pg.LabelItem(justify='right')
        self.window.addItem(self.label, row=0, col=0)
        self.plot = self.window.addPlot(row=1, col=0, title='Test')
        x = np.linspace(-100, 100, 1000)
        self.data = np.sin(x)/x
        self.plot.plot(self.data)
        self.plot.setLabel('left', 'y')
        self.plot.setLabel('bottom', 'x')
        self.plot.showGrid(x=True, y=True)
        self.plot.addItem(self.vLine, ignoreBounds=True)
        self.plot.addItem(self.hLine, ignoreBounds=True)
        self.proxyMouse = pg.SignalProxy(self.plot.scene().sigMouseClicked, slot=self.mouseClicked)
        self.proxyKey = pg.SignalProxy(self.plot.getViewBox().sigkeyPressEvent, slot=self.keyPressed)
        self.proxyRange = pg.SignalProxy(self.plot.sigRangeChanged, slot=self.rangeChanged)

    def mouseClicked(self, ev):
        if ev[0].button() != 1: return
        pos = ev[0].scenePos()
        mousePoint = self.plot.getViewBox().mapSceneToView(pos)
        viewRange = self.plot.getViewBox().viewRange()
        if not (viewRange[0][0] <= mousePoint.x() and mousePoint.x() <= viewRange[0][1] and viewRange[1][0] <= mousePoint.y() and mousePoint.y() <= viewRange[1][1]): return
        self.label.setText("x=%f, y=%f" % (
            mousePoint.x(), mousePoint.y()))
        self.vLine.setPos(mousePoint.x())
        self.hLine.setPos(mousePoint.y())

    def sizeROI(self, viewRange):
        diffx = viewRange[0][1] - viewRange[0][0]
        diffy = viewRange[1][1] - viewRange[1][0]
        sizex = 0.005 * diffx
        sizey = 0.1 * diffy
        return (sizex,sizey)

    def keyPressed(self, ev):
        if ev[0] != QtCore.Qt.Key_Return: return
        x = self.vLine.pos().x()
        x = int(round(x, 0))
        if 0 <= x and x < len(self.data):
            y = self.data[x]
            self.label.setText("x=%f, y=%f" % (x, y))
            viewRange = self.plot.getViewBox().viewRange()
            (sizex,sizey) = self.sizeROI(viewRange)
            roi = pg.ROI((x,y), size=(sizex,sizey), removable=True)
            roi.addTranslateHandle((1,1))
            roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
            text = pg.TextItem("(%f,%f)" % (x,y), color="y", anchor=(0,2), fill="b")
            text.setParentItem(roi)
            roi.sigRemoveRequested.connect(self.remove)
            roi.sigRegionChanged.connect(self.move)
            roi.sigClicked.connect(self.clicked)
            self.plot.addItem(roi)

    def rangeChanged(self, arg):
        vb = arg[0]
        if vb == self.plot.getViewBox():
            viewRange = arg[1]
            (sizex,sizey) = self.sizeROI(viewRange)
            for item in self.plot.items:
                if isinstance(item, pg.ROI):
                    item.setSize((sizex,sizey))
       
    def remove(self, roi):
        self.plot.removeItem(roi)

    def move(self, roi):
        for item in self.plot.items:
            if isinstance(item, pg.ROI):
                item.setZValue(0)
        roi.setZValue(1)
        x = roi.pos()[0]
        x = int(round(x, 0))
        if x < 0:
            x = 0
        elif x >= len(self.data):
            x = len(self.data)-1
        y = self.data[x]
        roi.setPos((x,y), update=False)
        for item in roi.childItems():
            if isinstance(item, pg.TextItem):
                item.setText("(%f,%f)" % (x,y), color="y")
           
    def clicked(self, roi, ev):
        for item in self.plot.items:
            if isinstance(item, pg.ROI):
                item.setZValue(0)
        roi.setZValue(1)

def main():
    app = QApplication([])
    demo = MarkerDemo()
    app.exec_()
   
if __name__ == '__main__':
    main()

ivan....@gmail.com

unread,
May 20, 2016, 3:34:02 PM5/20/16
to pyqtgraph
I forgot to mention that in order to intercept the keyboard, I made a custom modification to graphicsItems/ViewBox.py:
I added the line:
sigkeyPressEvent = QtCore.Signal(object)
in class ViewBox, after the line
sigResized = QtCore.Signal(object)
and I added the line:
self.sigkeyPressEvent.emit(ev.key())
to the keyPressEvent method, at the start of the method.

This way I can intercept keyboard actions in a ViewBox. Luke, perhaps we might want to consider supporting this in the future?
Reply all
Reply to author
Forward
0 new messages