Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save a plot occupying the whole page of a PDF file

I want to create a multi-page PDF file for printing using python. I would prefer to do this with a library that could work well in any environment so I tried with matplotlib.

I can produce the desired figure of A4 size, and create a multi-page PDF file, but the figure artist shrinks and does not occupy the whole page.

I can either produce a PDF of A4-sized pages with padding around the figure, or a PDF file with smaller page size (the page shrinks around the figure to remove the padding, with the figure remaining the same size).

Can someone help?

(I don't think this is duplicated, there are other questoions about the padding but not with the same aim as I have and I could not see any answer that worked for me - I have been searching around for days!)

What I have tried so far following a few SO questions/answers is:

  • Using bbox_inches='tight', pad_inches=0 in the pyplot.savefig() function, which gives a smaller than A4 PDF page with no padding.

(Strangely, this works well with EPS format, which prints straight into real size in A4 paper, but I don't know how to do a multi-page file out of that.)

  • If I do not use bbox_inches='tight', pad_inches=0, the figure size seems more or less the same but the padding around fills the space to make an A4 page.

  • Using fig.tight_layout(rect=[0,0,1,1]) or fig.tight_layout(pad=0), which keeps the padding.

This example code produces the figure in EPS (A4 no padding), PDF (A4 with padding) and TIFF (no padding but smaller size, so when you print it has padding):

import matplotlib.pyplot as plt
from matplotlib import gridspec

def plot_form_page():
    # A4 canvas
    fig_width_cm = 21                                # A4 page
    fig_height_cm = 29.7
    inches_per_cm = 1 / 2.54                         # Convert cm to inches
    fig_width = fig_width_cm * inches_per_cm         # width in inches
    fig_height = fig_height_cm * inches_per_cm       # height in inches
    fig_size = [fig_width, fig_height]

    plt.rc('text', usetex=False) # so that LaTeX is not needed when creating a PDF with PdfPages later on
    fig = plt.figure()
    fig.set_size_inches(fig_size)
    fig.set_facecolor('#9999ff')

    gs = gridspec.GridSpec(29, 21, wspace=0.1, hspace=0.1)    

    # external axis
    ax0 = fig.add_subplot(gs[:, :])
    # for side in ["top", "bottom", "left", "right"]:
    #     ax0.spines[side].set_visible(False)

    # header
    ax1 = fig.add_subplot(gs[1:4, 1:4])
    ax1.set_facecolor('#bbffbb')

    ax2 = fig.add_subplot(gs[1:4, 4:-6])
    ax2.set_facecolor('#ffbbff')

    ax3 = fig.add_subplot(gs[1:4, -6:-1])
    ax3.set_facecolor('#00bbff')

    # form
    ax4, ax5, ax6, ax7   = [fig.add_subplot(gs[ 5+(i*6):10+(i*6), 1:10]) for i in range(4)]    
    [ax8, ax9, ax10, ax11] = [fig.add_subplot(gs[ 5+(i*6):10+(i*6), 11:20]) for i in range(4)]
    axes = [ax4, ax8, ax5, ax9, ax6, ax10, ax7, ax11]
    for ax in axes:
        ax.set_facecolor('#bbbbbb')

    for ax in fig.axes:
        ax.set_xticks([])
        ax.set_yticks([])

    print(fig.properties()['figwidth'], fig.properties()['figheight'])

    # from previous tests:
    #fig.tight_layout(pad=0)

plot_form_page()

plt.savefig('page.tiff',
            dpi=300,
            orientation='portrait',
            box_inches='tight', pad_inches=0)
plt.savefig('page.pdf',
            dpi=300,
            orientation='portrait',
            bbox_inches='tight', pad_inches=0)
plt.savefig('page.eps',
            dpi=300,
            orientation='portrait',
            papertype='a4',
            bbox_inches='tight', pad_inches=0)

fig.properties() give me the right size for A4 in inches:

fig.properties()['figwidth']
fig.properties()['figheight']

8.26771653543307 11.69291338582677

But the figure shrinks to allow padding when it comes to printing

You can see the effects in the PDF file here:

https://www.dropbox.com/s/naqy9vj4ht6g2mv/Screenshot%202019-04-16%20at%2011.46.48.png?dl=0

With EPS is less of a problem:

https://www.dropbox.com/s/umycd8ofwod3nk5/Screenshot%202019-04-16%20at%2012.05.52.png?dl=0

(but again, I don't know how to create a multi-page EPS)

With the TIFF file I get the same:

https://www.dropbox.com/s/eid1zstbm954121/Screenshot%202019-04-16%20at%2012.11.59.png?dl=0

And this is not necessarily because the printing software adds the padding - the figure now is actually smaller, as it is 1929 × 2655 pixels at 300dpi, this makes it 6.43 × 8.85 in - certainly smaller than A4.

EDIT: This, of course, can be solved by printing with the option 'fit the page' but this could work with any image that has the proportions of an A4, I want to have control over the size, if that is possible.

like image 275
Joaquin Avatar asked Nov 06 '22 18:11

Joaquin


People also ask

How do I save a plot as a PDF?

Plot the data frame with 'o' and 'rx' style. To save the file in PDF format, use savefig() method where the image name is myImagePDF. pdf, format = ”pdf”. To show the image, use the plt.

How do I save multiple graphs in the same PDF in Python?

Plot the second line using plot() method. Initialize a variable, filename, to make a pdf file. Create a user-defined function save_multi_image() to save multiple images in a PDF file. Call the save_multi_image() function with filename.

How do I save a plot output in Python?

Saving a plot on your disk as an image file Now if you want to save matplotlib figures as image files programmatically, then all you need is matplotlib. pyplot. savefig() function. Simply pass the desired filename (and even location) and the figure will be stored on your disk.


1 Answers

Thanks to the comments of @ImportanceOfBeingErnest, and a bit of trial and error, I found a solution that creates an A4-shaped figure that when is saved as an A4 PDF page, it occupies the whole page (not 100% true, there is a very small margin, but it serves the purpose).

It just required to add gs.tight_layout(fig, pad=0). I do not fully understand why, but I suspect that when I was trying to eliminate the padding of the figure object (with plt.savefig(..., bbox_inches='tight', pad_inches=0) or fig.tight_layout(...)), the figure might reach the border of the A4 page, but the GridSpec would not, so the padding I was seeing was between the boundaries of the GridSpec and the figure, not with the border of the paper. When the padding between GridSpec and figure is set to 0, and the figure has A4 size, things seem to work as I wanted.

(I still do not understand why in some cases the page of the PDF file would shrink in size but I am going to leave it here).

This is the code that does what I want, if you create the PDF file, open, and examine the properties, the size is A4, with the rectangles close to the border. WIth the other options, there was either a wide margin or a smaller page size.

import matplotlib.pyplot as plt
from matplotlib import gridspec

def plot_form_page():
    # A4 canvas
    fig_width_cm = 21                                # A4 page
    fig_height_cm = 29.7
    inches_per_cm = 1 / 2.54                         # Convert cm to inches
    fig_width = fig_width_cm * inches_per_cm         # width in inches
    fig_height = fig_height_cm * inches_per_cm       # height in inches
    fig_size = [fig_width, fig_height]

    plt.rc('text', usetex=False) # so that LaTeX is not needed when creating a PDF with PdfPages later on
    fig = plt.figure()
    fig.set_size_inches(fig_size)
    fig.set_facecolor('#9999ff')

    gs = gridspec.GridSpec(29, 21, wspace=0.1, hspace=0.1)    

    # external axis
    ax0 = fig.add_subplot(gs[:, :])
    # for side in ["top", "bottom", "left", "right"]:
    #     ax0.spines[side].set_visible(False)

    # header
    ax1 = fig.add_subplot(gs[1:4, 1:4])
    ax1.set_facecolor('#bbffbb')

    ax2 = fig.add_subplot(gs[1:4, 4:-6])
    ax2.set_facecolor('#ffbbff')

    ax3 = fig.add_subplot(gs[1:4, -6:-1])
    ax3.set_facecolor('#00bbff')

    # form
    ax4, ax5, ax6, ax7   = [fig.add_subplot(gs[ 5+(i*6):10+(i*6), 1:10]) for i in range(4)]    
    [ax8, ax9, ax10, ax11] = [fig.add_subplot(gs[ 5+(i*6):10+(i*6), 11:20]) for i in range(4)]
    axes = [ax4, ax8, ax5, ax9, ax6, ax10, ax7, ax11]
    for ax in axes:
        ax.set_facecolor('#bbbbbb')

    for ax in fig.axes:
        ax.set_xticks([])
        ax.set_yticks([])

    #print(fig.properties()['figwidth'], fig.properties()['figheight'])

    gs.tight_layout(fig, pad=0)

plot_form_page()

plt.savefig('page.pdf',
            dpi=300,
            orientation='portrait')
like image 128
Joaquin Avatar answered Nov 14 '22 21:11

Joaquin