How to Show Cursor Position in PyQtGraph Embedded in PyQt5

Add a crosshair overlay that tracks the mouse cursor on your PyQtGraph plots
Heads up! You've already completed this tutorial.

When you embed a PyQtGraph plot inside a PyQt5 application, you might want to give your users a visual indicator of exactly where the cursor is on the plot. A common way to do this is with a crosshair — two thin lines (one vertical, one horizontal) that follow the mouse as it moves over the chart.

In this tutorial, we'll walk through how to add a crosshair to a PyQtGraph PlotWidget and keep it updated as the mouse moves. We'll also display the current cursor coordinates as text on the plot, so users can read the exact values.

Crosshair displayed on a PyQtGraph plot embedded in a PyQt5 window

Setting Up the Basic Plot

Let's start with a simple PyQt5 application that displays a line chart using PyQtGraph. If you've followed the embedding PyQtGraph tutorial, this will look familiar.

python
from PyQt5 import QtWidgets
import pyqtgraph as pg
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]

        self.graphWidget.setBackground("w")
        self.graphWidget.setTitle("Your Title Here", color="b", size="30pt")

        styles = {"color": "#f00", "font-size": "20px"}
        self.graphWidget.setLabel("left", "Temperature (°C)", **styles)
        self.graphWidget.setLabel("bottom", "Hour (H)", **styles)

        self.graphWidget.addLegend()
        self.graphWidget.showGrid(x=True, y=True)
        self.graphWidget.setXRange(0, 10, padding=0)
        self.graphWidget.setYRange(20, 55, padding=0)

        pen = pg.mkPen(color=(255, 0, 0))
        self.graphWidget.plot(
            hour,
            temperature,
            name="Sensor 1",
            pen=pen,
            symbol="+",
            symbolSize=30,
            symbolBrush="b",
        )


app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())

This gives us a styled plot with labeled axes, a grid, and a single data series. Now we can add the crosshair on top.

Adding the Crosshair Lines

PyQtGraph provides InfiniteLine, a line item that stretches across the entire plot in one direction. By creating two of these — one vertical (angle=90) and one horizontal (angle=0) — we get a crosshair.

Add the following lines inside __init__, after the call to self.graphWidget.plot(...):

python
# Add crosshair lines.
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)

The angle=90 line is vertical, and angle=0 is horizontal. We pass movable=False because we don't want the user to drag them — they should follow the mouse automatically. The ignoreBounds=True argument tells PyQtGraph not to consider these lines when auto-scaling the view, which prevents the plot from jumping around.

At this point the lines exist, but they just sit at position zero. We need to move them whenever the mouse moves.

Tracking Mouse Movement with SignalProxy

PyQtGraph's scene emits a sigMouseMoved signal every time the mouse moves over the plot area. We could connect directly to it, but that would fire for every single pixel the cursor crosses — potentially hundreds of times per second. That's more updates than we need and can slow things down.

Instead, we use pg.SignalProxy to throttle the signal to a reasonable rate (60 updates per second is plenty for a smooth crosshair). Add this line right after the crosshair setup:

python
self.proxy = pg.SignalProxy(
    self.graphWidget.scene().sigMouseMoved,
    rateLimit=60,
    slot=self.mouse_moved,
)

We store the proxy as self.proxy so it doesn't get garbage-collected. The slot parameter tells it which method to call when a mouse movement comes through.

Now let's write that mouse_moved method. Add it to the MainWindow class:

python
def mouse_moved(self, e):
    pos = e[0]
    if self.graphWidget.sceneBoundingRect().contains(pos):
        mouse_point = self.graphWidget.getPlotItem().vb.mapSceneToView(pos)
        self.crosshair_v.setPos(mouse_point.x())
        self.crosshair_h.setPos(mouse_point.y())

Here's what's happening in this method:

  1. The signal gives us a tuple, and e[0] is the position of the mouse in scene coordinates (pixel coordinates within the graphics scene).
  2. We check that the mouse is actually inside the plot area using sceneBoundingRect().contains(pos). This prevents the crosshair from updating when the cursor is over the axis labels or legend.
  3. We convert the scene coordinates to view coordinates (the actual data values on the axes) using mapSceneToView. This is what makes the crosshair line up correctly with the data.
  4. We update each InfiniteLine to the new position.

Displaying Coordinates as Text

A crosshair is helpful, but showing the actual x/y values makes it even more useful. We can add a TextItem to the plot that updates alongside the crosshair.

First, create the text item in __init__, alongside the crosshair lines:

python
self.cursor_label = pg.TextItem(text="", color="k", anchor=(0, 1))
self.graphWidget.addItem(self.cursor_label, ignoreBounds=True)

The anchor=(0, 1) places the text so its bottom-left corner sits at the position we assign. Using color="k" gives us black text, which works well on our white background.

Then update the mouse_moved method to set the text and position of the label:

python
def mouse_moved(self, e):
    pos = e[0]
    if self.graphWidget.sceneBoundingRect().contains(pos):
        mouse_point = self.graphWidget.getPlotItem().vb.mapSceneToView(pos)
        self.crosshair_v.setPos(mouse_point.x())
        self.crosshair_h.setPos(mouse_point.y())
        self.cursor_label.setText(
            f"x={mouse_point.x():.2f}, y={mouse_point.y():.2f}"
        )
        self.cursor_label.setPos(mouse_point.x(), mouse_point.y())

Now the label follows the cursor and shows the coordinates rounded to two decimal places.

Complete Working Example

Here's the full application with the crosshair and coordinate label:

python
from PyQt5 import QtWidgets
import pyqtgraph as pg
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]

        # Style the plot.
        self.graphWidget.setBackground("w")
        self.graphWidget.setTitle("Your Title Here", color="b", size="30pt")

        styles = {"color": "#f00", "font-size": "20px"}
        self.graphWidget.setLabel("left", "Temperature (°C)", **styles)
        self.graphWidget.setLabel("bottom", "Hour (H)", **styles)

        self.graphWidget.addLegend()
        self.graphWidget.showGrid(x=True, y=True)
        self.graphWidget.setXRange(0, 10, padding=0)
        self.graphWidget.setYRange(20, 55, padding=0)

        # Plot data.
        pen = pg.mkPen(color=(255, 0, 0))
        self.graphWidget.plot(
            hour,
            temperature,
            name="Sensor 1",
            pen=pen,
            symbol="+",
            symbolSize=30,
            symbolBrush="b",
        )

        # Add crosshair lines.
        self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
        self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
        self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
        self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)

        # Add a text label for coordinates.
        self.cursor_label = pg.TextItem(text="", color="k", anchor=(0, 1))
        self.graphWidget.addItem(self.cursor_label, ignoreBounds=True)

        # Use a SignalProxy to throttle mouse move events.
        self.proxy = pg.SignalProxy(
            self.graphWidget.scene().sigMouseMoved,
            rateLimit=60,
            slot=self.mouse_moved,
        )

    def mouse_moved(self, e):
        pos = e[0]
        if self.graphWidget.sceneBoundingRect().contains(pos):
            mouse_point = self.graphWidget.getPlotItem().vb.mapSceneToView(pos)
            self.crosshair_v.setPos(mouse_point.x())
            self.crosshair_h.setPos(mouse_point.y())
            self.cursor_label.setText(
                f"x={mouse_point.x():.2f}, y={mouse_point.y():.2f}"
            )
            self.cursor_label.setPos(mouse_point.x(), mouse_point.y())


app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())

Run this and move your mouse over the plot. You'll see the crosshair lines follow the cursor, and the text label shows the current coordinates in data-space.

Customizing the Crosshair Appearance

You can style the crosshair lines with a custom pen. For example, to make them dashed and semi-transparent:

python
crosshair_pen = pg.mkPen(color=(0, 0, 0, 120), width=1, style=pg.QtCore.Qt.DashLine)
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False, pen=crosshair_pen)
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False, pen=crosshair_pen)

The fourth value in the color tuple (0, 0, 0, 120) is the alpha (transparency), where 255 is fully opaque and 0 is invisible. A semi-transparent dashed line gives a subtler look that won't distract from your data.

You can also customize the text label's font size and background:

python
self.cursor_label = pg.TextItem(
    text="",
    color="k",
    anchor=(0, 1),
    fill=pg.mkBrush(255, 255, 255, 180),
)

Adding a fill brush gives the label a semi-transparent white background, making it easier to read when it overlaps with data points or grid lines.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Create GUI Applications with Python & Qt5 by Martin Fitzpatrick

(PyQt5 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

How to Show Cursor Position in PyQtGraph Embedded in PyQt5 was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.