PyPlot:在子图中生成共享图例,但在循环中嵌入时图例未正确显示。(PDF后端)
我刚开始学习使用Matplotlib,之前用的是Matlab。我在Windows 7上通过python(x,y)运行Spyder。
我写了一个小脚本,主要是为了:
- 打开几个包含表格数据的Excel电子表格
- 为每个表生成几个饼图,每个饼图总结Excel中的一行数据
- 将每组饼图绘制在输出文档的单独页面上,使用
PdfPages
后端。
因为所有的饼图都有相同的图例,所以我想在每个页面上只显示一个“共享”的图例。我找到的解决方案是:matplotlib - 在单独的子图中显示图例。把每个饼图放在自己的subplot
中,然后在另一个区域生成一个“虚拟”的饼图,生成图例,再用set_visible(False)
隐藏所有饼图的部分。
第一次循环(也就是第一个Excel电子表格和第一组饼图)没问题。但是后续的循环生成的图例有文本标签,但却没有颜色框。示例可以在这个Imgur链接查看(抱歉,我还不能发图片,因为我刚来Stackoverflow)。https://i.stack.imgur.com/O0ufi.png
这个问题似乎影响了PdfPages
后端生成的输出,但对默认的图形后端(TkAgg
? 不确定Spyder默认用的是哪个)没有影响。你可以在我下面简化的脚本中看到这一点,通过注释掉PDFfile.savefig()
并取消注释plt.show()
。
总的来说,这个问题似乎是因为.set_visible()
方法在循环中被“记住”了……但它只影响PdfPages
后端,而不影响图形后端。我感到非常困惑,但希望这篇帖子对其他人有帮助。任何帮助都非常感谢 :-)
import xlrd, numpy as np, matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
PDFfile = PdfPages('output.pdf')
for i in range(4):
pieData = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 0, 1, 2]]
pieSliceLabels = ['A', 'B', 'C', 'D']
pieLabels = ['1', '2', '3']
# now, generate some pie plots
plt.figure(0, figsize=(6,2))
numPies = len(pieLabels)
for thisPie in range(numPies):
ax = plt.subplot(1,4,1+thisPie)
plt.title(pieLabels[thisPie])
fracs = pieData[thisPie]
plt.pie(fracs)
# Since all three pie plots share the same data labels, I'd rather just
# generate a single legend that they all share.
# The method I used comes from here:
# https://stackoverflow.com/questions/11140311/matplotlib-legend-in-separate-subplot
# First, generate a "dummy pie" containing same data as last pie.
plt.subplot(1,4,4)
DummyPie = plt.pie(fracs)
# next, generate a big ol' legend
plt.legend(pieSliceLabels, loc='center',prop={'size':'small'})
# finally, hide the pie.
for Pieces in DummyPie:
for LittlePiece in Pieces:
LittlePiece.set_visible(False)
# NOW, HERE'S WHERE IT GETS WEIRD...
# Leave the following line uncommented:
PDFfile.savefig()
# ... and a PDF is generated where the legend colors are shown
# properly on the first page, but not thereafter!
# On the other hand.... comment out "PDF.savefig()" above, and
# uncomment the following line:
# plt.show()
# ...and the figures will be generated onscreen, WITHOUT the legend
# problem!!
PDFfile.close()
1 个回答
很奇怪,这个问题只在 PdfPages
后端出现,其实是因为你在重复使用同一个图形对象,但没有清空它。
最好每次都创建一个新的图形。如果你的PDF页面很多,可能只用一个图形会更合理,但每次都要清空它(比如用 plt.clf()
或 fig.clf()
)。
试试直接调用:
plt.figure(figsize=(6,2))
而不是:
plt.figure(0, figsize=(6,2))
另外,如果你不想让饼图看起来“被压扁”,记得把子图的比例设置为1(可以用 ax.axis('equal')
或 plt.axis('equal')
)。
补充:这里有一个使用matplotlib面向对象接口的例子。在我看来,这样可以更容易地管理这些内容:
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
pie_data = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 0, 1, 2]]
pie_slice_labels = ['A', 'B', 'C', 'D']
pie_labels = ['1', '2', '3']
PDFfile = PdfPages('output.pdf')
for i in range(4):
fig, axes = plt.subplots(ncols=len(pie_labels) + 1, figsize=(6,2))
for ax, data, label in zip(axes, pie_data, pie_labels):
wedges, labels = ax.pie(data)
ax.set(title=label, aspect=1)
# Instead of creating a dummy pie, just use the artists from the last one.
axes[-1].legend(wedges, pie_slice_labels, loc='center', fontsize='small')
axes[-1].axis('off')
# Alternately, you could do something like this to place the legend. If you
# use this, you should remove the +1 from ncols above.
# fig.legend(wedges, pie_slice_labels, loc='center right', fontsize='small')
# fig.subplots_adjust(right=0.8) # Make room for the legend.
PDFfile.savefig()
PDFfile.close()