问题
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