如何在matplotlib中以美学方式显示通用数量的轴?

时间:2018-08-02 15:20:45

标签: python user-interface matplotlib pyqt pyqt5

我想做一个简单的GUI,允许用户从图中添加或删除任何数量的迹线。看起来像这样:

enter image description here

我遇到的问题:

  • 对于许多通用图,我不知道如何使轴不相互重叠。
  • 当我绘制多条迹线,然后删除除一条迹线以外的所有迹线时,出于某些原因,将显示两个轴。 每条轨迹始终应显示一个轴。

是否可以解决这些问题?您可以在下面找到我的代码。我认为,唯一应该更改的功能是update_canvas()。要进行尝试,只需使用所需的变量数修改name_vars中的列表main。其余示例代码是独立的。

import numpy as np
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure

class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(ApplicationWindow, self).__init__(parent)
        global name_vars
        self.x = np.array([1,2,3,4,5])
        self.y = np.random.random((5, len(name_vars)))
        self.num_vars = np.size(self.y,1)
        self.name_vars = name_vars

        self.tags_on = [0] * self.num_vars
        self.colors = ['#1F77B4','#FF7F0E','#2CA02C','#D62728','#9467BD',
                       '#8C564B','#E377C2','#F7F7F7','#BCBD22','#17BECF']

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

        canvas = FigureCanvas(Figure(figsize=(10, 10)))
        self.canvas_ax = canvas.figure.subplots()
        self.canvas_ax.set_xlabel("Time")
        self.canvas_ax_twin = []

        self.list_tags = QtWidgets.QComboBox(self)
        for name in self.name_vars:
            self.list_tags.addItem(name)
        button_add = QtWidgets.QPushButton('Add', self)
        button_remove = QtWidgets.QPushButton('Remove', self)
        button_add.clicked.connect(self.add_plot)
        button_remove.clicked.connect(self.remove_plot)

        layout = QtWidgets.QGridLayout(self._main)
        layout.addWidget(canvas, 0, 0)
        dropdown_layout = QtWidgets.QHBoxLayout()
        dropdown_layout.addWidget(self.list_tags)
        dropdown_layout.addWidget(button_add)
        dropdown_layout.addWidget(button_remove)
        layout.addLayout(dropdown_layout, 1, 0)
        self.show()

    def add_plot(self):
        selected_tag = self.list_tags.currentIndex()
        self.tags_on[selected_tag] = 1
        self.update_canvas()

    def remove_plot(self):
        selected_tag = self.list_tags.currentIndex()
        self.tags_on[selected_tag] = 0
        self.update_canvas()

    def update_canvas(self):
        # Delete all traces
        self.canvas_ax.clear()
        [i.clear() for i in self.canvas_ax_twin]
        self.canvas_ax_twin = []
        num_plots = 0
        for ii in range(self.num_vars):
            if self.tags_on[ii] == 1:
                # If it's not the first trace, create a twin axis
                if num_plots != 0:
                    self.canvas_ax_twin.append(self.canvas_ax.twinx())
                    self.canvas_ax_twin[-1].plot(self.x, self.y[:,ii], self.colors[num_plots])
                    self.canvas_ax_twin[-1].set_ylabel(self.name_vars[ii])
                    self.canvas_ax_twin[-1].yaxis.label.set_color(self.colors[num_plots])
                    self.canvas_ax_twin[-1].tick_params(axis='y', colors=self.colors[num_plots])
                    num_plots += 1
                # If it's the first trace, use the original axis
                else:
                    self.canvas_ax.plot(self.x, self.y[:,ii], self.colors[num_plots])
                    self.canvas_ax.set_ylabel(self.name_vars[ii])
                    self.canvas_ax.yaxis.label.set_color(self.colors[num_plots])
                    self.canvas_ax.tick_params(axis='y', colors=self.colors[num_plots])
                    num_plots += 1
        # Show the final plot
        self.canvas_ax.figure.canvas.draw()

if __name__ == '__main__':
    # Edit the number of elements in name_vars to try the code
    name_vars = ['V1','V2','V3','V4']
    app = QtWidgets.QApplication([])
    ex = ApplicationWindow()
    ex.show()
    app.exec_()

1 个答案:

答案 0 :(得分:2)

我建议将逻辑与实际绘图分开。这使后续操作变得更容易。这解决了关于不删除所有轴的第二个问题。

可以通过将附加双轴的位置设置为与轴有一定距离来解决有关不重叠轴的问题,具体取决于您拥有多少根轴。

ax.spines["right"].set_position(("axes", 1+(n-1)*0.1))

其中n是从0开始的轴编号。应排除主轴(n = 0),并且第一个轴将停留在位置1。其他轴以0.1的步长定位。

然后有意义的是,还要调整主轴的右边距,以便为多余的棘刺留出足够的空间。

import numpy as np
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure

class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None, name_vars=[]):
        super(ApplicationWindow, self).__init__(parent)

        self.x = np.array([1,2,3,4,5])
        self.y = np.random.random((5, len(name_vars)))
        self.num_vars = np.size(self.y,1)
        self.name_vars = name_vars
        self.tags_on = [0] * self.num_vars

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

        self.figure = Figure(figsize=(10, 10))
        canvas = FigureCanvas(self.figure)
        self.left = self.figure.subplotpars.left
        self.right = self.figure.subplotpars.right
        self.canvas_ax = canvas.figure.subplots()
        self.canvas_ax.set_xlabel("Time")
        self.axes = [self.canvas_ax]

        self.list_tags = QtWidgets.QComboBox(self)
        for name in self.name_vars:
            self.list_tags.addItem(name)
        button_add = QtWidgets.QPushButton('Add', self)
        button_remove = QtWidgets.QPushButton('Remove', self)
        button_add.clicked.connect(self.add_plot)
        button_remove.clicked.connect(self.remove_plot)

        layout = QtWidgets.QGridLayout(self._main)
        layout.addWidget(canvas, 0, 0)
        dropdown_layout = QtWidgets.QHBoxLayout()
        dropdown_layout.addWidget(self.list_tags)
        dropdown_layout.addWidget(button_add)
        dropdown_layout.addWidget(button_remove)
        layout.addLayout(dropdown_layout, 1, 0)
        self.show()

    def add_plot(self):
        selected_tag = self.list_tags.currentIndex()
        self.tags_on[selected_tag] = 1
        self.update_canvas()

    def remove_plot(self):
        selected_tag = self.list_tags.currentIndex()
        self.tags_on[selected_tag] = 0
        self.update_canvas()

    def create_nth_axes(self, n, dataset):
        if n == 0:
            ax = self.canvas_ax
        else:
            ax = self.canvas_ax.twinx()
            ax.spines["right"].set_position(("axes", 1+(n-1)*0.1))
            for direction in ["left", "bottom", "top"]:
                ax.spines[direction].set_visible(False)
            # adjust subplotparams to make space for new axes spine
            new_right = (self.right-self.left)/(1+(n-1)*0.1)+self.left
            self.figure.subplots_adjust(right=new_right)
        color = next(self.canvas_ax._get_lines.prop_cycler)['color']
        ax.set_ylabel(self.name_vars[dataset], color=color)
        ax.plot(self.x, self.y[:,dataset], color=color)
        return ax

    def clear_canvas(self):
        # Clear main axes
        self.canvas_ax.clear()
        # clear and remove other axes
        for ax in self.axes[1:]:
            ax.clear()
            ax.remove()
        self.axes = [self.canvas_ax] 
        self.figure.subplots_adjust(right=0.9)

    def update_canvas(self):
        self.clear_canvas()
        k = 0
        for i, tag in enumerate(self.tags_on):
            if tag:
                ax = self.create_nth_axes(k, i)
                if k > 0:
                    self.axes.append(ax)
                k += 1  
        self.canvas_ax.figure.canvas.draw()

if __name__ == '__main__':
    # Edit the number of elements in name_vars to try the code
    name_vars = ['V1','V2','V3','V4']
    app = QtWidgets.QApplication([])
    ex = ApplicationWindow(name_vars=name_vars)
    ex.show()
    app.exec_()