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:
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.
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')