[Python] Why does pyqtgraph PlotWidget stutter/lags when I pan, zoom with mouse while graph...

Discussão em 'Python' iniciado por Stack, Junho 18, 2025.

  1. Stack

    Stack Membro Participativo


    • Python 3.12


    • pyqtgraph 0.13.7


    • PySide 6.9.0


    • OS: Windows 11

    I am making an application with PySide6 and pyqtgraph. It has 2 graphs which are updated every second by different QThread via Signal. This Signal sends data to a method/slot in main QApplication which is slightly processed then few UI elements are updated according to that data and then data is displayed in graph.

    Main QApplication Setup:

    class TimeAxisItem(pg.AxisItem):
    """Converts time stamps to pyqtgraph Axis"""

    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    def tickStrings(self, values, scale, spacing):
    """converts timestamp to pyqtgraph axis"""
    return [
    datetime.datetime.fromtimestamp(value).strftime("%Y-%m-%d %H:%M:%S")
    for value in values
    ]


    class MainWindow(QMainWindow):
    """Main Window of the Dashboard."""

    trained_wells_signal = Signal(list)

    def __init__(self):
    super().__init__()
    ...

    self._vfm_tags_checkboxes = []
    for i in range(1, 31):
    inner_body = self.ui.stackedWidget.findChild(
    QWidget, f"VFM_Tag_No_{i}_body"
    )
    for checkbox in inner_body.findChildren(QCheckBox):
    self._vfm_tags_checkboxes.append(checkbox)


    # This is how I setup my first graph (called in __init__). Second graph is also made same.

    def _setup_upper_graph(self):

    if self.gwlayout is None:
    self.gwlayout = QGridLayout(self.ui.Graph_Widget_1)
    if self.plot_graph is None:
    bottom_axis = TimeAxisItem(orientation="bottom")

    self.plot_graph = pg.PlotWidget(
    parent=self.ui.Graph_Widget_1, axisItems={"bottom": bottom_axis}
    )
    # self.plot_graph.setMouseEnabled(x = True, y = True)
    self.plot_graph.setBackground(self.graph_color)

    self.plot_graph.setTitle("VFM", size="large", color="white")

    self.plot_graph.setLabel("left", "Y")
    self.plot_graph.setLabel("bottom", "Time")

    self.plot_graph.addLegend()
    self.plot_graph.showGrid(True, True)

    if not self.layout_flag:
    self.gwlayout.addWidget(self.plot_graph)
    self.layout_flag = True


    # This is the slot for first graph
    def predictions_update(self, predictions: pd.DataFrame):

    predictions = predictions[0]
    for well in predictions:
    lates = predictions[well]
    if self.predictions.get(well) is None:
    self.predictions[well] = lates
    else:
    self.predictions[well] = pd.concat(
    [self.predictions[well], lates], ignore_index=True
    ).iloc[-400:]

    if self.whole_db_flag:
    self.make_graph()


    def make_graph(self):
    """Check for checked boxes and plot graph"""
    self.check_and_put()


    local_data = []

    for checkbox in self._vfm_tags_checkboxes:
    if not checkbox.isChecked():
    continue
    name = checkbox.objectName()
    tag = self.get_pred_tag(name)
    if tag in self.predictions:
    d = [
    [
    self.time_workout(the_time)
    for the_time in self.predictions[tag]["Time"]
    ][-400:],
    list(self.predictions[tag][self._get_data_name_u_graph(name)])[
    -400:
    ],
    self.get_legend(name),
    ]

    local_data.append(d)

    for xy_data in local_data:
    x_data, y_data, name = xy_data

    if name in self.plot_lines:
    line = self.plot_lines[name]
    line.setData(x_data, y_data)
    else:
    color = self._get_color(name)
    line = self.plot_graph.plot(
    x_data,
    y_data,
    pen={"color": color, "width": 3},
    name=name,
    )
    self.plot_lines[name] = line

    def check_and_put(self) -> None:
    for checkbox in self._trained_vfm_chk:
    if checkbox.isChecked():
    i = int(re.search(r"\d", checkbox.objectName()).group())
    chk_name = checkbox.objectName()
    label_name = chk_name.replace("CheckBox", "Val")

    label = self._vfm_labels[chk_name]
    data_name, fluid = self.pred_value_label(label_name)

    # Update the QLabel in the main UI
    pred_dict = self.predictions.get(data_name)
    if pred_dict is None:
    print(f"[!] {data_name} is None...")
    continue

    label.setText(f"{pred_dict[fluid].iloc[-1]}")


    # This is the slot for second graph (this is processing before making graph)
    def vfm_update(self, return_data) -> None:

    latest_data, rates = return_data

    for i in range(1, 6):
    label = self.ui.Model_Test_Body1.findChild(QLabel, f"MT_Val_X{i}")
    if i == 4:
    data = str(latest_data[0][i + 1])
    data = f"{data[:2]}.{data[2:4]}"
    label.setText(data)
    continue
    label.setText(str(latest_data[0][i + 1]))

    self.ui.Test_Well_Selection_Oil_Val.setText(str(round(rates[0], 3)))
    self.ui.Test_Well_Selection_Gas_Val.setText(str(round(rates[1], 3)))
    self.ui.Test_Well_Selection_Water_Val.setText(str(round(rates[2], 3)))

    ###############################

    latest_pd = pd.DataFrame(latest_data, columns=self.columns)
    self.bottom_graph_time.append(self.time_workout(latest_pd["Time"][0]))

    if not self.whole_db_flag:

    self.whole_db = latest_pd

    if self.data_for_training_flag:
    self.data_to_train = latest_pd
    self.whole_db_flag = True
    print(f"{self.whole_db_flag=}")
    else:
    self.whole_db = pd.concat([self.whole_db, latest_pd], ignore_index=True)

    if self.data_for_training_flag:
    self.data_to_train = pd.concat(
    [self.data_to_train, latest_pd], ignore_index=True
    )

    if self.whole_db_flag:
    length_wholedb = len(self.whole_db)
    if length_wholedb > 400:
    self.whole_db = self.whole_db.iloc[1:]

    self.predictions_namespace.latest_value = self.whole_db.iloc[-1]
    self.make_graph_bottom()
    self.mpfm_update() # This method is updating 5 labels so I am not including it here.


    def make_graph_bottom(self):
    """Check for checked boxes and plot graph"""
    # print("Somethings checked.")
    # start_time = perf_counter()
    local_data = []
    for checkbox in self.mpfm_checkboxes:
    if not checkbox.isChecked():
    continue
    name = checkbox.objectName().split("_")[1]
    local_data.append(
    [
    self.bottom_graph_time,
    [int(d) for d in self.whole_db[self._get_data_name_b_graph(name)]],
    name.title(),
    ]
    )


    for xy_data in local_data:
    x_data, y_data, name = xy_data

    if name in self.bottom_plot_lines:
    line = self.bottom_plot_lines[name]
    line.setData(x_data, y_data)
    else:
    line = self.bottom_graph.plot(
    x_data,
    y_data,
    pen={"color": self.bottom_graph_colors[name], "width": 3},
    name=name,
    )
    self.bottom_plot_lines[name] = line

    if not self.layout_flag_1:
    self.gwlayout_1.addWidget(self.bottom_graph)
    self.layout_flag_1 = True



    So, my question is when I zoom, pan my graph it very few times drags/zoom smoothly (I experience it only when graph is not being updated) but most of the time it is so jittery and laggy, it looks like it jumped to that state or starts to move after a second or so or simply it doesn't respond to my input at all. Despite I am using .setData() method and only 400 values, why is this happening?

    I didn't find this particular problem any where and every tutorial and issue suggests .setData() method to update graph.

    Using the perf_counter(), both slots are taking 0.01 to 0.05 seconds to complete their operations including graph.

    • I switched from clearing the graph for every update to .setData().
    • Exported all database calls to QThread and get data back via Signal.
    • Using deque for TimeAxisItem (x plane).
    • Storing all UI elements that should update to a list so I don't have to do .findChildren everytime.

    And it still lags whenever I move the graph. Although the graph updates very fine if I don't touch it.

    Continue reading...

Compartilhe esta Página