Как правильно добавить оси в фигуру matplotlib при сохранении ограниченного макета? - PullRequest
0 голосов
/ 13 февраля 2020

У меня есть программа, в которой я хотел бы поменять местами разные оси на одном и том же рисунке, чтобы построить различные наборы данных относительно друг друга, либо в полярной, либо в прямолинейной системе координат в зависимости от набора данных. Метод change_axes реализует эту функциональность, в то время как методы swap_axes, clf_swap, hide_swap и new_subplot_swap показывают различные способы переключения между осями, хотя swap_axes - единственный способ получить желаемый результат. , Код основан на этом посте .

Есть ли лучший способ сделать это? Использование axes.set_visible или axes.set_alpha ничего не делает для меня и даже вызывает ошибку по какой-то причине (AttributeError: 'NoneType' object has no attribute 'get_points'). Я не понимаю, почему это не работает, но это был бы гораздо более простой способ «добавления» и «удаления» осей.

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 Ответ

1 голос
/ 13 февраля 2020

При использовании текущей версии разработки 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()
...