PyPlot:在子图中生成共享图例,但在循环中嵌入时图例未正确显示。(PDF后端)

1 投票
1 回答
1238 浏览
提问于 2025-04-17 14:20

我刚开始学习使用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 个回答

0

很奇怪,这个问题只在 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()

撰写回答