首页 > 解决方案 > 无法同时在多个 pyqtgraph 图形上显示鼠标位置标记

问题描述

TIA。

github:https ://github.com/b3st0fth3w0rst/data_plotter

我在使 x 轴(垂直线)标记出现在所有 3 个图形上时遇到了一些问题,指示鼠标当前的位置。最初我希望这些图表彼此独立,但现在不是那么多了。

我想做的是:

  1. 当鼠标在 3 个图形中的任何一个中移动时,垂直 x 轴线同时在所有 3 个图形上跟随鼠标。目前它们是独立的,我已经设置了它,以便只有“点击/聚焦”图表显示运动。

  2. 当鼠标在 3 个图表中的任何一个中移动时,水平 y 轴线应该只在当前“点击/聚焦”图表中跟随鼠标,就像我现在设置它的方式一样(鼠标标记只显示焦点图表中的移动)。

笔记:

  1. 单击“加载数据”以加载 csv 文件。
  2. 选择要在图表上绘制的复选框项目。
  3. 单击 3 个图表中的任何一个,开始在那里绘制 cb 项目。

我对使用 python 构建 GUI 应用程序很陌生,我觉得我错过了一些简单的东西。我尝试在所有 3 个图表上执行 mouseMoved() 代码,特别是在我设置 v/hLine 的地方,但这似乎只在最后一个图表中激活鼠标标记(参见第二次提交。第一次提交是我的初始工作设计)。

test_main.py

import csv
import os
import pandas as pd
import pyqtgraph as pg
import sys

from test_plot import Plot
from PyQt5 import QtWidgets, QtCore, QtGui
from ui.test_window import Ui_MainWindow

CURRENT_FOCUSED_PLOT_ID = None
PREVIOUS_FOCUSED_PLOT_ID = None
CURRENT_EVENT = None
PLOT_EVENT_MAP = {}


class Connector:
    """
    Class that connects the compiled test_window.py to test_main.py.
    This class contains the modified code that does not get overridden by mod.py.
    """

    class MyPlotWidget(pg.PlotWidget):
        """
        Overridden PlotWidget class to add a mouseclick handler to each of the graphs created.
        """

        def __init__(self, connector):
            super().__init__()
            self.scene().sigMouseClicked.connect(self.mouse_clicked_event_handler)
            self.main_myplotwidget = connector

        def mouse_clicked_event_handler(self):
            """
            Mouse clicked event handler for graphs
            """

            global CURRENT_FOCUSED_PLOT_ID
            CURRENT_FOCUSED_PLOT_ID = id(self)

            if CURRENT_FOCUSED_PLOT_ID != PREVIOUS_FOCUSED_PLOT_ID:
                self.main_myplotwidget.update_graphs_checkboxes()

            mouse_point_x = self.main_myplotwidget.current_mouse_point[0]

        def mouseMoveEvent(self, ev):
            """
            Mouse movement on graph event handler

            :param ev: QMouseEvent - Event object for the graphs
            """

            try:
                # if id(self) == CURRENT_FOCUSED_PLOT_ID:
                mouse_point = self.main_myplotwidget.plot.mouse_moved(ev,
                                                                      self.main_myplotwidget.get_current_graph(),
                                                                      self.main_myplotwidget.plot_widgets)

                if 0 <= mouse_point[0] <= self.main_myplotwidget.x_range \
                        and 0 <= mouse_point[1] <= self.main_myplotwidget.y_range:
                    self.main_myplotwidget.ui.mouse_point_info_label.setText('Mouse Point: <span '
                                                                             'style="font-size:10pt" '
                                                                             'style="color:red">X={}</span>, Y={}'
                                                                             .format(mouse_point[0], mouse_point[1])
                                                                             )
                    self.main_myplotwidget.current_mouse_point = mouse_point
            except Exception as e:
                print(e)

    def __init__(self):
        self.model = QtGui.QStandardItemModel()
        self.app = QtWidgets.QApplication(sys.argv)
        self.main_window = QtWidgets.QMainWindow()
        self.ui = Ui_MainWindow(self.main_window)

        self.csv_data = {}
        self.column_name_cb = {}
        self.graph_cb_data = {}
        self.plot_widgets = []
        self.x_range = 0
        self.y_range = 0
        self.current_mouse_point = (0, 0)

        self.ui_setup()
        self.plot = Plot(self.ui.mouse_point_info_label)

    def ui_setup(self):
        """
        Setups up the UI with additional/modified parameters that may or may not be available in test_window.py
        """

        self.ui.load_data_pb.clicked.connect(self.load_data)

        self.ui.plot_widget_1 = self.MyPlotWidget(self)
        self.ui.plot_widget_1.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.ui.plot_widget_1.setStyleSheet("background-color: rgb(0, 0, 0);")
        self.ui.plot_widget_1.setObjectName("plot_widget_1")
        self.ui.gridLayout.addWidget(self.ui.plot_widget_1, 0, 0, 1, 1)

        self.ui.plot_widget_2 = self.MyPlotWidget(self)
        self.ui.plot_widget_2.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.ui.plot_widget_2.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.ui.plot_widget_2.setObjectName("plot_widget_2")
        self.ui.gridLayout_7.addWidget(self.ui.plot_widget_2, 0, 0, 1, 1)

        self.ui.plot_widget_3 = self.MyPlotWidget(self)
        self.ui.plot_widget_3.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.ui.plot_widget_3.setStyleSheet("background-color: rgb(0, 85, 255);")
        self.ui.plot_widget_3.setObjectName("plot_widget_3")
        self.ui.gridLayout_8.addWidget(self.ui.plot_widget_3, 0, 0, 1, 1)

        self.plot_widgets.append(self.ui.plot_widget_1)
        self.plot_widgets.append(self.ui.plot_widget_2)
        self.plot_widgets.append(self.ui.plot_widget_3)

        # Set the default graph focused to the top graph
        global CURRENT_FOCUSED_PLOT_ID
        CURRENT_FOCUSED_PLOT_ID = id(self.ui.plot_widget_1)
        global PREVIOUS_FOCUSED_PLOT_ID
        PREVIOUS_FOCUSED_PLOT_ID = CURRENT_FOCUSED_PLOT_ID

    def get_current_graph(self):
        """
        Returns the currently focused graph object.

        :return: MyPlotWidget - The currently focused plot.
        """
        for graph in self.plot_widgets:
            if CURRENT_FOCUSED_PLOT_ID == id(graph):
                return graph

    def plot_data(self, graph, data):
        """
        Plots the given data on the given graph.

        :param graph: MyPlotWidget - PlotWidget object to plot data on.
        :param data: Dict - Dictionary containing the data name and data set.
        """

        self.plot.plot_values(graph, data, self.x_range, self.y_range, self.plot_widgets)

    def load_data(self, file_path=None):
        """
        Handles opening a CSV file
        """

        # if not file_path:
        #     csv_file_name, _ = QFileDialog.getOpenFileName(self.main_window)
        # else:
        #     csv_file_name = file_path

        csv_file_name = 'resource\\test_data.csv'

        if os.path.exists(csv_file_name):
            for plot in self.plot_widgets:
                plot.clear()

            with open(csv_file_name, "r") as f:
                csv_reader = csv.reader(f, delimiter=',')
                columns = next(csv_reader)
                df = pd.read_csv(csv_file_name, usecols=columns)

                for item in columns:
                    if item != 'Name':
                        self.csv_data[item] = df[item]

                for row in csv.reader(f):
                    items = [QtGui.QStandardItem(field) for field in row]
                    self.model.appendRow(items)

            # Create Column checkboxes
            for column in self.csv_data.keys():
                self.column_name_cb['check_box_{}'.format(column)] = QtWidgets.QCheckBox(
                    self.ui.scrollAreaWidgetContents)
                self.column_name_cb.get('check_box_{}'.format(column)).setObjectName(
                    "check_box_{}".format(column))
                self.column_name_cb.get('check_box_{}'.format(column)).setText(column)
                self.column_name_cb.get('check_box_{}'.format(column)).stateChanged.connect(
                    self.checkbox_state_changed_csv)
                self.ui.verticalLayout_3.addWidget(self.column_name_cb.get('check_box_{}'.format(column)))

            self.x_range = len(self.csv_data.get(list(self.csv_data.keys())[0]))
            self.y_range = 150

            self.ui.plot_widget_1.setEnabled(True)
            self.ui.plot_widget_2.setEnabled(True)
            self.ui.plot_widget_3.setEnabled(True)

    def checkbox_state_changed_csv(self):
        """
        Handles when the checkboxes state change for the Pot tab.
        """

        columns_to_plot = []
        data_to_plot = {}

        for checkbox in self.column_name_cb.items():
            if checkbox[1].isChecked():
                columns_to_plot.append(checkbox[0])

        for item in columns_to_plot:
            item = item.split('_')[-1]
            data_to_plot[item] = self.csv_data.get(item).values

        if PREVIOUS_FOCUSED_PLOT_ID == CURRENT_FOCUSED_PLOT_ID and data_to_plot:
            self.graph_cb_data[CURRENT_FOCUSED_PLOT_ID] = data_to_plot
            self.plot_data(self.get_current_graph(), data_to_plot)
        elif PREVIOUS_FOCUSED_PLOT_ID != CURRENT_FOCUSED_PLOT_ID:
            self.plot_data(self.get_current_graph(), data_to_plot)
        else:
            self.plot.clear_plot_widget(self.get_current_graph())

    def update_graphs_checkboxes(self):
        """
        Updates the checkboxes in the CSV tab to match the currently focused graph.
        A mouse click on any particular graph will show the associated checkboxes.
        """

        for checkbox in self.column_name_cb.items():
            checkbox[1].setChecked(False)

        global PREVIOUS_FOCUSED_PLOT_ID
        if PREVIOUS_FOCUSED_PLOT_ID != CURRENT_FOCUSED_PLOT_ID and \
                CURRENT_FOCUSED_PLOT_ID not in self.graph_cb_data.keys():
            self.plot_data(self.get_current_graph(), {})

        for cb_data in self.graph_cb_data.items():
            if cb_data[0] == id(self.get_current_graph()):
                data_list = list(cb_data[1].keys())
                for checkbox in self.column_name_cb.items():
                    if checkbox[0].split('_')[-1] in data_list:
                        checkbox[1].setChecked(True)
                    else:
                        checkbox[1].setChecked(False)

        PREVIOUS_FOCUSED_PLOT_ID = CURRENT_FOCUSED_PLOT_ID


if __name__ == "__main__":
    a = Connector()
    a.main_window.show()
    sys.exit(a.app.exec_())

test_plot.py

import numpy as np
import random
import pyqtgraph as pg

from PyQt5 import QtCore


class Plot:

    def __init__(self, mouse_point_info_label):
        self.widget = None
        self.data = None
        self.x_range = None
        self.y_range = None
        self.color_map = []
        self.vb = []
        self.hLine = None
        self.vLine = None
        self.event_graph_map = {}
        self.mouse_point_info_label = mouse_point_info_label

    def get_color(self, item_to_color):
        """
        Returns a random color.

        :param item_to_color: Specific data item to color. self.color_map keep track of colors used for items. If an
                              item has already been colored we'll reuse the same color, otherwise pick a new color.
        :return: Tuple - RGB values representing a color (255,255,255).
        """

        color = (255, 255, 255)
        for item in self.data.items():
            if item[0] == item_to_color:
                existing_color = [i for i, v in enumerate(self.color_map) if v[0] == item[0]]
                if existing_color is not None and existing_color:
                    return self.color_map[existing_color[0]][1]
                else:
                    r = random.randint(0, 255)
                    g = random.randint(0, 255)
                    b = random.randint(0, 255)
                    color = (r, g, b)
                    self.color_map.append((item[0], color))
                    return color
        self.color_map.append((item_to_color, color))
        return color

    def mouse_moved(self, evt, current_graph, graph_objs, x=None, y=None):
        self.widget = current_graph
        mouse_point_x = 0
        mouse_point_y = 0

        if x is not None:
            self.vLine.setPos(x)

            if y is not None:
                self.hLine.setPos(y)
            else:
                self.hLine.setPos(0)

            return x, y
        else:
            x = evt.x()
            y = evt.y()

        pos = QtCore.QPointF(x, y)

        for item in self.vb:
            # if self.widget.sceneBoundingRect().contains(pos):
            try:
                mouse_point = item[1].mapSceneToView(pos)
                mouse_point_x = int(mouse_point.x())
                mouse_point_y = int(mouse_point.y())

                # if 0 <= mouse_point_x <= self.x_range and 0 <= mouse_point_y <= self.y_range:
                self.vLine.setPos(mouse_point.x())
                self.hLine.setPos(mouse_point.y())
            except Exception as e:
                pass

        return mouse_point_x, mouse_point_y

    def plot_values(self, plot_widget, data, x_range, y_range, graph_objs):
        """
        Plots the given values on the given graph.

        :param plot_widget: MyPlotWidget - Overridden PlotWidget method in main.Connector() class. Graph object.
        :param data: Dict - Containing the name of the data to plot and the data itself.
        :param x_range: Int - Max x-axis value.
        :param y_range: Int - Max y-axis value.
        """

        self.widget = plot_widget
        self.data = data

        # Setup the 3 graphs, but only plot the data on the focused graph
        for item in graph_objs:
            self.x_range = x_range
            self.y_range = y_range
            self.widget.setXRange(0, self.x_range)
            self.widget.setYRange(0, self.y_range)
            self.widget.showGrid(x=True, y=True)

            # self.widget.setLabel('left', 'Value', units='y')
            self.widget.setLabel('bottom', 'Frames')

        self.widget.addLegend()
        self.widget.clear()

        for item in self.data.items():
            line = self.widget.plot(np.insert(item[1], 0, item[1][0]), pen=self.get_color(item[0]),
                                    symbolPen=self.get_color(item[0]), symbol='o', symbolSize=1, name=item[0])
        self.marker(graph_objs)

    def marker(self, graph_objs):

        # Paint the marker for all 3 graphs and add the mouseMoved signal
        for item in graph_objs:
            # Crosshair
            self.vLine = pg.InfiniteLine(angle=90, movable=False)
            self.hLine = pg.InfiniteLine(angle=0, movable=False)
            item.addItem(self.vLine, ignoreBounds=True)
            item.addItem(self.hLine, ignoreBounds=True)

            self.vb.append((item, item.plotItem.vb))
            proxy = pg.SignalProxy(item.scene().sigMouseMoved, rateLimit=60, slot=self.mouse_moved)
            item.scene().sigMouseMoved.connect(self.mouse_moved)

    def clear_plot_widget(self, plot_widget):
        plot_widget.clear()

标签: pythonpyqtpyqtgraph

解决方案


推荐阅读