在保持受限布局的同时将轴重新添加到 matplotlib 图形的正确方法是什么?
What is the proper way of re-adding axes to a matplotlib figure while maintaining a constrained layout?
我有一个程序,我想在同一个图中的不同轴之间交换,以便根据数据集在极坐标系或直线坐标系中绘制不同的数据集。
change_axes
方法实现了此功能,而 swap_axes
、clf_swap
、hide_swap
和 new_subplot_swap
方法显示了尝试在轴之间交换的不同方式,尽管 swap_axes
是唯一产生所需结果的。该代码基于 this post。
有更好的方法吗?使用 axes.set_visible
或 axes.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()
我有一个程序,我想在同一个图中的不同轴之间交换,以便根据数据集在极坐标系或直线坐标系中绘制不同的数据集。
change_axes
方法实现了此功能,而 swap_axes
、clf_swap
、hide_swap
和 new_subplot_swap
方法显示了尝试在轴之间交换的不同方式,尽管 swap_axes
是唯一产生所需结果的。该代码基于 this post。
有更好的方法吗?使用 axes.set_visible
或 axes.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()