1

I am designing a GUI in which whenever I click a button, the figure inside the GUI will be updated. This figure contains two y-axes with a common x-axis. I am using twinx function inside matplotlib. Everything is working as expected, except the ytick of the 2nd y-axis.

Here is my code:

import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from numpy import *
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(650, 400)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton_15 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_15.setGeometry(QtCore.QRect(430, 50, 191, 23))
        self.pushButton_15.setObjectName("pushButton_15")
        self.pushButton_15.clicked.connect(self.PlotOnCanvas)
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setGeometry(QtCore.QRect(20, 25, 421, 290))
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.frame_2 = QtWidgets.QFrame(self.frame)
        self.frame_2.setGeometry(QtCore.QRect(9, 10, 401, 271))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
        self.frame_2.setSizePolicy(sizePolicy)
        self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame_2.setObjectName("frame_2")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_2)
        self.horizontalLayout_2.setObjectName("HorizontalLayout_2")
        self.figure, self.ax = plt.subplots()
        self.canvas = FigureCanvas(self.figure)
        self.horizontalLayout_2.addWidget(self.canvas)
        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)


    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton_15.setText(_translate("MainWindow", "test"))


    def PlotOnCanvas(self):
        self.ax.clear()
        x = np.linspace(0, 4 * pi, 1000)
        y1 = np.sin(2 * pi * np.random.rand() * 2 * x)
        y2 = np.sin(2 * pi * np.random.rand() * 2 * x)
        colory2 = 'tab:red'
        colory1 = 'tab:blue'
        ax1 = self.ax
        ax2 = ax1.twinx()
        ax1.set_xlabel('x axis', fontsize=8)
        ax1.xaxis.set_tick_params(labelsize=8)
        ax1.set_ylabel('y1 axis', color=colory1, fontsize=8)
        ax2.set_ylabel('y2 axis', color=colory2, fontsize=8)
        ax1.yaxis.set_tick_params(labelcolor=colory1, labelsize=8)
        ax2.yaxis.set_tick_params(labelcolor=colory2, labelsize=8)
        ax1.set_yticks([-1, -0.5, 0, 0.5, 1])
        ax2.set_yticks([-1, -0.5, 0, 0.5, 1])
        ax1.set_ylim(-1.2, 1.2)
        ax2.set_ylim(-1.2, 1.2)
        l1, = ax1.plot(x, y1, lw=2, color=colory1)
        l2, = ax2.plot(x, y2, lw=2, color=colory2)
        plt.legend([l1, l2], ['y1', 'y2'], fontsize=8, loc="upper right")
        plt.title('y1/y2', fontsize=8)
        plt.tight_layout()
        self.canvas.draw()
        ax2.clear()


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

and here is the output:

After 1st click

After 1st click

After multiple clicks

After multiple clicks

After 1st click, I can get both axis in the format that I want. However, after the 2nd click, the 2nd axis start to behave strangely. Its range changes from (-1,1) to (0,1); in addition, its ticks (0,0.2, ..., 1) starts to print on top of each other. As a result, its font becomes thicker.

I tried clearing the axis and both axes using ax.cla() and ax.clf() but it didn't work.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Rex
  • 13
  • 1
  • 6
  • I can reproduce this with a recent version of matplotlib but not with a very old one (2.x), so it may be a bug or a change in implementation. Unfortunately, I don't use matplotlib that much, but it seems clear that the y-axis is not actually cleared as it should: what you see is not just the "changed" range, but a *sum* of the existing one and the other. In any case, please consider that you're ***NOT*** expected to edit pyuic generated files, so please follow the official guidelines about [using Designer](//www.riverbankcomputing.com/static/Docs/PyQt5/designer.html) instead. – musicamante Jun 07 '23 at 21:14

1 Answers1

1

It looks like you keep making a new twinx axis every click. In setupUi function, I'd change self.figure, self.ax = plt.subplots() to self.figure, self.ax1 = plt.subplots() and then add a line afterwards that is self.ax2 = self.ax1.twinx(). This will only use twinx once in the setup.

Now that you have those two axes defined, you can change, your PlotOnCanvas function to only reference those two axes. To do this, you can create convenience variables ax1 = self.ax1 and ax2 = self.ax2, removing ax2 = ax1.twinx(), which was dealt with in the other function. You can then call ax1.clear() and ax2.clear() before running the rest of the function. With those changes, I think it should now update properly because it is referencing the proper axes and not adding a new twinx axis every click.

To address the twinx's y-axis label moving to the wrong side, add ax2.yaxis.set_label_position("right") to the PlotOnCanvas function before calling ax2.set_ylabel().

Putting it all together, we have

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        # everything is the same until the plot creation
        # use twinx in the setup and reference the same ax2 afterward
        self.figure, self.ax1 = plt.subplots()
        self.ax2 = self.ax1.twinx()
        
        # everything else in this function is the same


    def PlotOnCanvas(self):
        # use the saved ax1 and ax2
        ax1 = self.ax1
        ax2 = self.ax2
        ax1.clear()
        ax2.clear()
        
        # other code until the axis labels
        # add this line before setting the y-label
        ax2.yaxis.set_label_position("right")
        ax2.set_ylabel('y2 axis', color=colory2, fontsize=8)

        # everything else unchanged
jared
  • 4,165
  • 1
  • 8
  • 31
  • Thanks jared. I did what you suggested and it solved my issue. Now, one thing new come up. After the 2nd click, the 2nd axis label (i.e. "y2 axis") moves to the left side and remain there. As a result, I have no y-label in the right handside of my figure and two y-label on the left handside. Other details are completely fine. It is only the y-label of the 2nd axis! – Rex Jun 07 '23 at 21:45
  • See my edited answer. – jared Jun 07 '23 at 21:54