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:
- Markers are created by clicking the hairline cursor to a specific position on the graph and pressing Return.
- Any number of markers can be added to a plot.
- 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.
- The marker has an attached TextItem that displays the x,y coordinate of the marker's position.
- 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.
- When the graph is zoomed in or out, the marker size changes dynamically.
- 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()