Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib linewidth when saving a PDF

I have a figure with some fairly delicate features that are sensitive to linewidth. I want to save this figure as a PDF that can be easily printed (i.e. no scaling on the receiver's side, just Command+P and go). Unfortunately, when I set figsize=(8.5,11) in order to correctly size the PDF for printing, matplotlib picks a very thick default linewidth and text size that mess up the plot (the legend is too large and the lines in the bar chart overlap). If I set figsize=(17,22) I get a very workable default linewidth and textsize after scaling the PDF to 50% for printing. This is what I have been using, but that solution has become unworkable due to politics and I really don't want to scale the PDF in illustrator every time I make a change.

If I could work with bitmaps I could achieve the desired result by setting figsize=(17,22) and setting dpi to half of the target dpi, but this does not work for PDFs since the dpi parameter seems to be ignored. I would like a PDF that

  • looks like boxes_good.png (the size-tricked bitmap with thin lines, small text)
  • has dimensions 8.5x11in (or prints like it does)
  • can be edited in illustrator (is not a bitmap wrapped in a pdf)

I can't help but suspect that there is an easy way to pull the "double size, half dpi" trick when saving as a PDF, but I gave up on getting that to work and started trying to directly manipulate the linewidths and textsizes. I succeeded in modifying textsize but not linewidth. Here is a record of the things I tried:

# Tried:
# fig.set_size_inches(17,22)
# fig.savefig('boxes.pdf', dpi=100,150,300)
#   dpi parameter has no effect on linewidth, text size, or the PDF's dimensions
# 'markerscale=.5' on plt.legend and pax.legend
#   no effect
# mp.rcParams['font.size']=8
#   worked! made text smaller, now for linewidth...
# mp.rcParams['lines.linewidth']=5
#   no effect
# fig.set_linewidth(5)
#   no effect
# pax.axhline(linewidth=5)
#   only changes x axis not box surrounding subplot
# fig.set_size_inches(8.5,11) immediately before plt.savefig('boxes.pdf')
#   identical results to calling plt.figure(figsize=(8.5,11)) in the first place

# I tried passing plt.figure(figsize=(17,22)) and swapping it to 8.5x11 using
# fig.set_size_inches right before saving, but the lines were thick and the text
# was large in the PDF, exactly as if I had set figsize=(8.5,11) to begin with

Here is the sourcefile (I have reduced the plot to essentials, so obvious styling workarounds probably aren't workable solutions)

import numpy as np
import matplotlib as mp
import matplotlib.pyplot as plt

x = np.arange(200)
bottom_red_bar = -np.random.random(200)
bottom_black_bar = np.random.random(200) * bottom_red_bar

fig = plt.figure()
for subplotnum in [1,2,3]:
    pax = plt.subplot(310+subplotnum)
    pax.set_ylim([-1,1])
    bot_rb = pax.bar(x,         bottom_red_bar,1,color='r')
    bot_bb = pax.bar(x+(1-.3)/2,bottom_black_bar,.3,color='k')
    pax.legend([bot_rb,bot_bb],['Filler Text 1','Filler Text 2'],loc=4)

fig.set_size_inches(8.5,11)
fig.savefig('boxes_bad.png',dpi=300)  # Lines are too thick

fig.set_size_inches(17,22)
fig.savefig('boxes_good.png',dpi=150)  # Lines are just right

fig.set_size_inches(8.5,11)
plt.savefig('boxes.pdf')  # Lines are too thick

fig.set_size_inches(17,22) # Lines are just right
plt.savefig('boxes.pdf')   # but the PDF needs scaling before printing

So I'm after a way to either adjust the linewidth of an entire figure or a way to have matplotlib save a pdf with dimension metadata different from figsize. Any suggestions?

like image 418
jjoonathan Avatar asked Apr 18 '13 21:04

jjoonathan


People also ask

How do I save a matplotlib figure as a PDF?

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. show() method.

How do you change linewidth in matplotlib?

Import the various modules and libraries you need for the plot: matplot library matplot , numpy , pyplot . Create your data set(s) to be plotted. In the plot() method after declaring the linewidth parameter, you assign it to any number value you want to represent the desired width of your plot.

How do I save a matplotlib figure without white space?

You can use either plt. margins(x=0) or ax. margins(x=0) to remove all margins on the x-axis.

How do I save multiple graphs in a 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.


2 Answers

Thanks Marius, I'll upvote as soon as I get 15 reputation required to do so. While your rcParams didn't quite match what I wanted to do, rcParams itself was the correct place to look so I listed rcParams containing 'linewidth' via rcParams.keys():

>>> [s for s in mp.rcParams.keys() if 'linewidth' in s]
['axes.linewidth', 'grid.linewidth', 'lines.linewidth', 'patch.linewidth']

After some experimentation, I matched up what each param controlled:
mp.rcParams['axes.linewidth']: the square surrounding the entire plot (not ticks, not the y=0 line)
mp.rcParams['grid.linewidth']: didn't test, presumably grid width
mp.rcParams['lines.linewidth']: the width of line plots made using pyplot.plot
mp.rcParams['patch.linewidth']: the width of rectangle strokes including the bars of a pyplot.bar plot, legends, and legend labels of bar plots
mp.rcParams['xtick.minor.width']: the linewidth of small xticks (yticks similar)
mp.rcParams['xtick.major.width']: the linewidth of large xticks (yticks similar)

The specific solution I wound up using was

mp.rcParams['axes.linewidth'] = .5
mp.rcParams['lines.linewidth'] = .5
mp.rcParams['patch.linewidth'] = .5
like image 193
jjoonathan Avatar answered Sep 27 '22 19:09

jjoonathan


I would suggest to adjust parameters like linewidth via the rcParams (or your matplotlibrc file):

# mp.rcParams['figure.figsize'] = fig_size  # set figure size
mp.rcParams['font.size'] = font_size
mp.rcParams['axes.labelsize'] = font_size
mp.rcParams['axes.linewidth'] = font_size / 12.
mp.rcParams['axes.titlesize'] = font_size
mp.rcParams['legend.fontsize'] = font_size
mp.rcParams['xtick.labelsize'] = font_size
mp.rcParams['ytick.labelsize'] = font_size

I normally use the standard figure.figsize which is (8,6) and a linewidth in the axes object that is 1/12 of the font size (eg. font_size = 16 when I include the plots in twocolumn papers).

Remember that vector graphics doesn't mean, that all lines and letters always have the same size when scaling. It means that you can scale without loosing quality or sharpness (roughly speaking).

like image 44
Marius Avatar answered Sep 27 '22 20:09

Marius