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

久未见 提交于 2020-03-05 00:23:50

问题


I have a program where I would like to swap between different axes in the same figure in order to plot different datasets against eachother, either in a polar or rectilinear coordinate system depending on the dataset. The change_axes method implements this functionality, while the swap_axes, clf_swap, hide_swap and new_subplot_swap methods show different ways of trying to swap between axes, although swap_axes is the only one that produces the desired result. The code is based on this post.

Is there a better way of doing this? Using axes.set_visible or axes.set_alpha does nothing for me and even produces an Error for some reason (AttributeError: 'NoneType' object has no attribute 'get_points'). I don't understand why this doesn't work, but it would have been a much easier way of 'adding' and 'removing' the axes.

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_())

回答1:


Using the current development version of matplotlib the error would not appear. But one would need to make sure to exclude the respective axes from the constrained layout mechanism.

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()

With any current matplotlib version, one could use tight_layout instead of constrained layout, and call it manually at each resize event.

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()


来源:https://stackoverflow.com/questions/60209169/what-is-the-proper-way-of-re-adding-axes-to-a-matplotlib-figure-while-maintainin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!