Search code examples
pythonpyqtpyqt5pyqtchart

How to add a crosshair to a pyqt5 graph


I need to add a crosshair, like here on the page.crosshair The crosshair should move behind the mouse. There are few examples on the Internet on this topic. There's "pyqtgraph" everywhere". I don't understand them. I want to use the button to add and remove the crosshair. Can I do with just one pyqt?

Сan use this code as a basis to add a crosshair.

from PyQt5 import QtCore, QtGui, QtWidgets, QtChart
from PyQt5.QtCore import *
from PyQt5.QtChart import *
import math
import numpy as np

mas =[1.33, 1.15, 1.55, 1.65, 1.64, 1.91, 1.33, 2.3, 1.5, 1.35, 2.52, 1.77, 1.7, 1.87, 2.0, 1.55, 1.73, 2.1,
              1.33, 1.15, 1.55, 1.92, 1.64, 1.91, 1.33, 1.71, 1.5, 1.35, 1.22, 1.77, 1.7, 1.87, 2.7, 1.55, 1.73, 2.1,
              1.33, 1.15, 1.55, 1.92, 1.64, 1.91, 1.33, 1.71, 1.5, 1.35, 1.22, 1.77, 1.7, 1.87, 2.0, 1.55, 1.73, 2.1]
x = len(mas)
x_ = x - 1

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, start = 1, parent=None):
        self.start = start
        super().__init__(parent)

        self.step = 30
        self._chart_view = QtChart.QChartView()
        self.scrollbar = QtWidgets.QScrollBar(
            QtCore.Qt.Horizontal,
            sliderMoved=self.onAxisSliderMoved,
            pageStep=self.step,
        )

        self.scrollbar.setRange(0, x_)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QVBoxLayout(central_widget)
        for w in (self._chart_view, self.scrollbar):
            lay.addWidget(w)

        self._chart = QtChart.QChart()
        self._line_serie = QtChart.QLineSeries()

        for i in range(0, len(mas)):
            self._line_serie.append(QtCore.QPointF(i, mas[i]))

        min_x, max_x = 0, x_
        self._chart.addSeries(self._line_serie)

        axisX = QValueAxis()
        axisX.setTickCount(5)
        axisX.setLabelFormat("%d")
        self._chart.addAxis(axisX, Qt.AlignBottom)
        self._line_serie.attachAxis(axisX)

        axisY = QValueAxis()
        self._chart.addAxis(axisY, Qt.AlignLeft)
        self._line_serie.attachAxis(axisY)

        self._chart.legend().hide()
        self._chart_view.setChart(self._chart)
        self.lims = np.array([min_x, max_x])
        self.onAxisSliderMoved(self.scrollbar.value())

        self.adjust_axes(self.start, 31)

    def adjust_axes(self, value_min, value_max):
        if value_min >= 0 and value_max >= 0 and value_max <= x_ and value_max > value_min:
            self._chart.axisX(self._line_serie).setRange(value_min, value_max)

    @QtCore.pyqtSlot(int)
    def onAxisSliderMoved(self, value):
        value2 = value + self.step
        value1 = value
        if value2 >= x_:
            value2 = x_
            value1 = value2 - self.step
        self.adjust_axes(math.floor(value1), math.ceil(value2))


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow(start = 0)
    w.show()
    sys.exit(app.exec_())

Solution

  • A possible solution is to override the drawForeground method of QChartView where the lines should be drawn based on the mouse position:

    class ChartView(QtChart.QChartView):
        def __init__(self, parent=None):
            super().__init__(parent)
            self._value_pos = QtCore.QPoint()
            self.setMouseTracking(True)
    
        def drawForeground(self, painter, rect):
            super().drawForeground(painter, rect)
            if self.chart() is None or self._value_pos.isNull():
                return
    
            pen = QtGui.QPen(QtGui.QColor("salmon"))
            pen.setWidth(8)
            painter.setPen(pen)
    
            area = self.chart().plotArea()
            sp = self.chart().mapToPosition(self._value_pos)
            x1 = QtCore.QPointF(area.left() + pen.width() / 2, sp.y())
            x2 = QtCore.QPointF(area.right() - pen.width() / 2, sp.y())
            y1 = QtCore.QPointF(sp.x(), area.top() + pen.width() / 2)
            y2 = QtCore.QPointF(sp.x(), area.bottom() - pen.width() / 2)
    
            if area.left() <= sp.x() <= area.right():
                painter.drawLine(y1, y2)
            if area.top() < sp.y() < area.bottom():
                painter.drawLine(x1, x2)
    
        def mouseMoveEvent(self, event):
            super().mouseMoveEvent(event)
            if self.chart() is None:
                return
            sp = self.mapToScene(event.pos())
            if self.chart().plotArea().contains(sp):
                self._value_pos = self.chart().mapToValue(sp)
                self.setCursor(QtCore.Qt.PointingHandCursor)
            else:
                self.unsetCursor()
            self.update()
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, start=1, parent=None):
            super().__init__(parent)
            self.start = start
            self._chart_view = ChartView()
            # ...

    enter image description here