在保持受限布局的同时将轴重新添加到 matplotlib 图形的正确方法是什么?

What is the proper way of re-adding axes to a matplotlib figure while maintaining a constrained layout?

我有一个程序,我想在同一个图中的不同轴之间交换,以便根据数据集在极坐标系或直线坐标系中绘制不同的数据集。 change_axes 方法实现了此功能,而 swap_axesclf_swaphide_swapnew_subplot_swap 方法显示了尝试在轴之间交换的不同方式,尽管 swap_axes 是唯一产生所​​需结果的。该代码基于 this post

有更好的方法吗?使用 axes.set_visibleaxes.set_alpha 对我没有任何作用,甚至由于某种原因 (AttributeError: 'NoneType' object has no attribute 'get_points') 会产生错误。我不明白为什么这不起作用,但 'adding' 和 'removing' 轴的方法会简单得多。

import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
    FigureCanvasQTAgg as FigureCanvas

from PyQt5.QtWidgets import (
    QApplication, QDialog, QGridLayout, QComboBox, QPushButton
)


class Test(QDialog):
    def __init__(self):
        super().__init__()
        self.lay = QGridLayout(self)
        self.fig, self.ax = plt.subplots(constrained_layout=True)

        self.ax2 = self.fig.add_subplot(111, projection='polar')
        # self.ax2 = plt.subplot(111, projection='polar')

        # uncomment this for 'hide_swap'
        # self.ax2.set_visible(False)

        self.canvas = FigureCanvas(self.fig)
        self.lay.addWidget(self.canvas)
        self.data = {
            'Dataset 1': np.random.normal(2, 0.5, 10000),
            'Dataset 2': np.random.binomial(10000, 0.3, 10000),
            'Dataset 3': np.random.uniform(0, 2*np.pi, 10000)
        }
        _, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)

        self.swapBtn = QPushButton('Swap')
        self.lay.addWidget(self.swapBtn, 1, 0)
        self.swapBtn.clicked.connect(self.swap_axes)
        self.clfSwapBtn = QPushButton('fig.clf() swap')
        self.clfSwapBtn.clicked.connect(self.clf_swap)
        self.lay.addWidget(self.clfSwapBtn, 2, 0)
        self.hideSwapBtn = QPushButton('Hide swap')
        self.hideSwapBtn.clicked.connect(self.hide_swap)
        self.lay.addWidget(self.hideSwapBtn, 3, 0)
        self.subplotSwapBtn = QPushButton('New subplot swap')
        self.subplotSwapBtn.clicked.connect(self.new_subplot_swap)
        self.lay.addWidget(self.subplotSwapBtn, 4, 0)
        self.xParam = QComboBox()
        self.xParam.addItem('Dataset 1')
        self.xParam.addItem('Dataset 2')
        self.xParam.addItem('Dataset 3')
        self.xParam.currentTextChanged.connect(self.change_axes)
        self.lay.addWidget(self.xParam)
        self.yParam = QComboBox()
        self.yParam.addItem('Distribution')
        self.yParam.addItem('Dataset 1')
        self.yParam.addItem('Dataset 2')
        self.yParam.addItem('Dataset 3')
        self.yParam.currentTextChanged.connect(self.change_axes)
        self.lay.addWidget(self.yParam)
        self.canvas.draw()

        # this is neccessary for
        # "self.ax2 = self.fig.add_subplot(111, projection='polar')", and for
        # some reason has to be called after 'canvas.draw', otherwise,
        # the constrained layout cannot be applied. comment this if using
        # "self.ax2 = plt.subplot(111, projection='polar')" or "hide_swap"
        self.ax2.remove()

    def change_axes(self):
        if self.yParam.currentText() == 'Distribution':
            if self.xParam.currentText() == 'Dataset 3':
                if self.fig.axes[0] == self.ax:
                    self.ax.remove()
                    self.ax2.figure = self.fig
                    self.fig.axes.append(self.ax2)
                    self.fig.add_axes(self.ax2)
                radii, theta = np.histogram(self.data['Dataset 3'], 100)
                width = np.diff(theta)
                self.fig.axes[0].cla()
                self.artists = self.ax2.bar(theta[:-1], radii, width=width)
            else:
                if self.fig.axes[0] == self.ax2:
                    self.ax2.remove()
                    self.ax.figure = self.fig
                    self.fig.axes.append(self.ax)
                    self.fig.add_axes(self.ax)
                self.fig.axes[0].cla()
                _, _, self.artists = self.ax.hist(
                    self.data[self.xParam.currentText()], 100
                )
        else:
            if (
                self.xParam.currentText() == 'Dataset 3'
                and self.fig.axes[0] == self.ax
            ):
                self.ax.remove()
                self.ax2.figure = self.fig
                self.fig.axes.append(self.ax2)
                self.fig.add_axes(self.ax2)
            elif (
                self.xParam.currentText() != 'Dataset 3'
                and self.fig.axes[0] == self.ax2
            ):
                self.ax2.remove()
                self.ax.figure = self.fig
                self.fig.axes.append(self.ax)
                self.fig.add_axes(self.ax)
            self.fig.axes[0].cla()
            self.artists = self.fig.axes[0].plot(
                self.data[self.xParam.currentText()],
                self.data[self.yParam.currentText()], 'o'
            )
        self.canvas.draw()

    def swap_axes(self):
        if self.fig.axes[0] == self.ax:
            self.ax.remove()
            self.ax2.figure = self.fig
            self.fig.axes.append(self.ax2)
            self.fig.add_axes(self.ax2)
        else:
            self.ax2.remove()
            self.ax.figure = self.fig
            self.fig.axes.append(self.ax)
            self.fig.add_axes(self.ax)
        self.canvas.draw()

    def clf_swap(self):
        if self.fig.axes[0] == self.ax:
            self.fig.clf()
            self.ax2.figure = self.fig
            self.fig.add_axes(self.ax2)
        else:
            self.fig.clf()
            self.ax.figure = self.fig
            _, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)
            self.fig.add_axes(self.ax)
        self.canvas.draw()

    def hide_swap(self):
        if self.ax.get_visible():
            self.ax.set_visible(False)
            self.ax2.set_visible(True)
            # self.ax.set_alpha(0)
            # self.ax2.set_alpha(1)
        else:
            self.ax.set_visible(True)
            self.ax2.set_visible(False)
            # self.ax.set_alpha(1)
            # self.ax2.set_alpha(0)
        self.canvas.draw()

    def new_subplot_swap(self):
        if self.fig.axes[0].name == 'rectilinear':
            self.ax.remove()
            self.ax2 = self.fig.add_subplot(111, projection='polar')
        else:
            self.ax2.remove()
            self.ax = self.fig.add_subplot(111)
            _, _, self.artists = self.ax.hist(self.data['Dataset 1'], 100)
        self.canvas.draw()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())

使用当前开发版本的matplotlib不会出现该错误。但是需要确保从约束布局机制中排除相应的轴。

import matplotlib.pyplot as plt

fig, ax = plt.subplots(constrained_layout=True)
ax2 = fig.add_subplot(111, projection="polar")
ax2.set_visible(False)
ax2.set_in_layout(False)

def swap(evt):
    if evt.key == "h":
        b = ax.get_visible()
        ax.set_visible(not b)
        ax.set_in_layout(not b)
        ax2.set_visible(b)
        ax2.set_in_layout(b)
        fig.canvas.draw_idle()

cid = fig.canvas.mpl_connect("key_press_event", swap)

plt.show()

对于任何当前的 matplotlib 版本,都可以使用 tight_layout 而不是约束布局,并在每次调整大小事件时手动调用它。

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax2 = fig.add_subplot(111, projection="polar")
ax2.set_visible(False)


def swap(evt):
    if evt.key == "h":
        b = ax.get_visible()
        ax.set_visible(not b)
        ax2.set_visible(b)
        fig.tight_layout()
        fig.canvas.draw_idle()

def onresize(evt):
    fig.tight_layout()

cid = fig.canvas.mpl_connect("key_press_event", swap)
cid = fig.canvas.mpl_connect("resize_event", onresize)
fig.tight_layout()    
plt.show()