pythonmatplotlibbackendpie-chartpdfpages

PyPlot: Trying to generate shared legend in a subplot, but legends don't appear properly when embedded in a loop. (PDF backend)


I'm just starting out with Matplotlib, switching from Matlab. I run Spyder through python(x,y) on Windows 7.

I wrote a little script whose job is to

Since all pie plots share the same legend, I wanted to show just one "shared" legend on each sheet. The solution I found is shown here: matplotlib - Legend in separate subplot Place each pie plot in its own subplot, then generate a "dummy" pie in another pane, generate a legend, then hide all the pie's pieces using set_visible(False).

The first loop iteration (i.e., first Excel spreadsheet and first page of pie plots) was fine. However, subsequent loops yielded legends with text labels, but without colored boxes. Example is shown at the following Imgur link (sorry, I can't post images yet since I'm new to Stackoverflow). https://i.sstatic.net/O0ufi.png

The problem seems to affect the output generated by the PdfPages backend, but not the default graphical backend (TkAgg? not sure which one Spyder uses by default). You can see this in my stripped-down script below by commenting out PDFfile.savefig() and uncommenting plt.show().

So, in summary, this problem seems to stem from the .set_visible() methods being "remembered" across loops... but it affects the PdfPages backend, not the graphical one. I'm utterly confused, but hoping this post makes sense to others. Any help appreciated :-)

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()

Solution

  • It's weird that it only shows up in the PdfPages backend, but it's because you're re-using the same figure object without clearing it.

    It's best to make a new figure each time. If you're going to have a large number of pages in the pdf, it may make more sense to only use one figure, but clear it each time (e.g. plt.clf() or fig.clf()).

    Try just calling:

    plt.figure(figsize=(6,2))
    

    Instead of:

    plt.figure(0, figsize=(6,2))
    

    Also, if you don't want the pie charts to look "squashed", be sure to set the subplot's aspect ratio to one (ax.axis('equal') or plt.axis('equal').


    Edit: Here's an example of using the OO interface to matplotlib. It makes some of this a bit easier to keep track of, i.m.o:

    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()