Matplolib fixing EXACT size of pdf file - matplotlib

I have a code that creates bar plots from a file. I save each plot in a pdf file with a fixed size that I set using rcParams (Landscape A4 format):
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 11 # Landscape A4 format inches
fig_size[1] = 8
plt.rcParams["figure.figsize"] = fig_size
This is the code to save the plot as pdf.
plt.savefig(filename, bbox_inches="tight", pad_inches=0.5, transparent=True, dpi=300)
I have tried dpi=100, dpi=600 with no change.
I have about 40 pdf files. However, the size of those pdf slightly varies from 10:35 x 8:36 to almost 10:47x 8:47 inches.
I am talking of the size of the page itself not the disk space that I understand depends on the number of pixel used to plot the file. My files are between 13-17 kb... and that is ok.
What is not ok, is that I can't combine the pdf files in a report as the sizes of the pdf pages do not match.
How can I set up the size of the pdf page to be exactly the same?
below a reproducible script:
"For StackOverFlow"
import matplotlib.pyplot as plt
SMALL_SIZE = 9
MEDIUM_SIZE = 14
BIGGER_SIZE = 18 #28
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('axes', titlesize=BIGGER_SIZE)
#x1 = [14, 10, 61, 15, 22]
#y1 = [-10, -2, 1, 8, 12]
#x1 = [0, 1.4, 1.47, 2.3, 2.6]
#y1 = [-1.51, -0.03, 0.04, 0.92, 1.23]
x1=[0.795466667, 1.02, 1.12, 1.155, 1.22, 1.459, 1.47, 1.81]
y1=[-1.50, -0.77, -0.45, -0.34, -0.13, 0.64, 0.68, 1.77]
barWidth = 1.2
r1 = [1.5* i for i in range(0, len(y1))]
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 11 #fig_size[0] = 11.69
fig_size[1] = 8 #fig_size[1] = 8.27
plt.rcParams["figure.figsize"] = fig_size
plt.margins(0.005, 0.005)
plt.bar(r1,y1, align='center', color='#9E0032', width=barWidth)
plt.xlabel('Z Code', fontweight='bold')
plt.ylabel('\n Z Value', fontweight='bold')
# Create names on the x-axis
plt.xticks(r1, x1, fontweight='bold')
plt.yticks(fontweight='bold')
title = "Specimen1"
plt.title(title, fontweight='bold')
filename = title + ".pdf"
#plt.savefig(filename, bbox_inches="tight", pad_inches=0.5, transparent=True, dpi=300)
plt.savefig(filename, pad_inches=0.5, transparent=True, dpi=300)
plt.show()
Thank you for the help

Related

Set real dimensione of a Chart in Matplotlib

I need to set the dimension of a chart exactly. I tried this, but the result is not what I expected (both if I set px and cm). In addiction, I would like to know how to export correctly the image.
import numpy as np
plt.rcParams['figure.dpi']=100
# create data
x = ['A', 'B', 'C', 'D']
y1 = np.array([10, 20, 10, 30])
y2 = np.array([20, 25, 15, 25])
y3 = np.array([12, 15, 19, 6])
y4 = np.array([10, 29, 13, 19])
# plot bars in stack manner
cm = 1/2.54 # centimeters in inches
px = 1/plt.rcParams['figure.dpi'] # pixel in inches
plt.figure(figsize=(800*px,1000*px))
plt.bar(x, y1, color='r')
plt.bar(x, y2, bottom=y1, color='b')
plt.bar(x, y3, bottom=y1+y2, color='y')
plt.bar(x, y4, bottom=y1+y2+y3, color='g')
plt.xlabel("Teams")
plt.ylabel("Score")
plt.legend(["Round 1", "Round 2", "Round 3", "Round 4"])
plt.title("Scores by Teams in 4 Rounds")
plt.show()
Dimensions expected: 800px x 1000 px, dpi= 100
I attach here a screenshot from Photoshop of the exported image
Not correct dimensions!
The Figure constructor accepts a tuple (numbers in inches) with a default of 80 dpi. You'll want to pass a dpi argument to change this
from matplotlib.figure import Figure
fig = Figure(figsize=(5, 4), dpi=80)
The above is 5 inches by 4 inches at 80dpi, which is 400px by 320px
if you want 800 by 1000 you can do
fig = Figure(figsize=(8, 10), dpi=100)
Exporting an image is as simple as
fig.savefig("MatPlotLib_Graph.png", dpi = 100)

How do I determine the [fig]size of a matplotlib.image.AxesImage in pixel?

This code renders the Lenna image with matplotlib,
import urllib
import matplotlib.pyplot as plt
imgurl = 'https://upload.wikimedia.org/wikipedia/en/thumb/7/7d/Lenna_%28test_image%29.png/330px-Lenna_%28test_image%29.png'
f = urllib.request.urlopen(imgurl)
img = plt.imread(f)
axi = plt.imshow(img)
where axi is an instance of matplotlib.image.AxesImage
How do I determine the [fig]size of the AxesImage in pixel? the expected value might (330, 330)
I tried axi.get_window_extent() and got
Bbox([[112.68, 36.00000000000003], [330.12, 253.44000000000003]])
Where do those values (112.68, 330.12) come from?
To get the raw image size
Use AxesImage.get_size():
axi.get_size()
# (330, 330)
To convert the axes extent into pixels
Adjust the window extent by Figure.dpi:
axi = plt.imshow(img)
fig = plt.gcf()
bbox = axi.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width = bbox.width * fig.dpi
height = bbox.height * fig.dpi
# 334.79999999999995 217.43999999999997
The reason this is not 330x330 is because of how plt.imshow() handles the aspect ratio. If you plot with aspect='auto', the underlying axes' shape becomes visible:
axi = plt.imshow(img, aspect='auto')
To coerce the underlying axes into desired shape
Manually define figsize and rect using the pixel dimensions and desired dpi:
width_px, height_px, _ = img.shape
dpi = 96
figsize = (width_px / dpi, height_px / dpi) # inches
rect = [0, 0, 1, 1] # [left, bottom, width, height] as fraction of figsize
fig = plt.figure(figsize=figsize, dpi=dpi) # in inches
axes = fig.add_axes(rect=rect)
axi = axes.imshow(img, aspect='auto')
Then the extent pixels will be exactly 330x330:
bbox = axi.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width = bbox.width * fig.dpi
height = bbox.height * fig.dpi
# 330.0 330.0
axi.get_size() gives (330,330) - why not use that?

Issue saving matplotlib plot with artist

I used the following code to generate The wind rose shares an axis with the artists which are Wedge patches.
fov = Wedge((0.51,0.43), 0.497, 174.5, 202.5, lw=1.5, facecolor="grey", edgecolor ='black', transform=ax.transAxes, alpha=0.2)
fov1 = Wedge((0.51,0.43), 0.497, 174.5, 202.5, lw=1.5, fill=None, edgecolor ='black', transform=ax.transAxes, alpha=1)
plt.hist([0, 1])
plt.close()
ax=WindroseAxes.from_ax()
ax.grid(linestyle="dashed", color="grey", zorder=0)
ax.bar(df['dir'], df['w_speed'],normed=True, opening=1, cmap = cm.magma_r, edgecolor='black', linewidth=0.5, bins=spd_bins, nsector=36, zorder= 3)
ax.set_legend(loc=(-0.12, 0.75), labels=spd_labels)
ax.set_yticks(np.arange(1, 12, step=3))
ax.set_yticklabels(np.arange(1, 12, step=3))
ax.add_artist(fov)
ax.add_artist(fov1)
plt.savefig(fname)
plt.show()
The plot includes an artists which gets moved when I save the figure in either of .pdf, .jpeg, .png, .eps formats.

Adding edges to bars in matplotlib

I have been following the example provided in:
https://matplotlib.org/examples/api/barchart_demo.html
My problem is that I want to add edges to the bars. But when I set the
linewidth=1, edgecolor='black'
parameters, the edges are only applied to the first pair of bars, leaving the remaining pairs unchanged.
"""
========
Barchart
========
A bar plot with errorbars and height labels on individual bars
"""
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std,linewidth=1, edgecolor='black')
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std, linewidth=1, edgecolor='black')
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
Thanks for your help.
David.

matplotlib + wxpython not sizing correctly with legend

I have a matplotlib figure embedded in a wxpython frame with a few sizers. Everything works fine until I include a legend but then the sizers don't seem to be working with the legend.
Even when I resize the window by dragging at the corner, the main figure changes size, but only the edge of the legend is ever shown.
That is, note that the legend is not visible in the wxFrame.
import wx
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from random import shuffle
class PlotFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title="Plot", size=(-1, -1))
self.main_panel = wx.Panel(self, -1)
self.plot_panel = PlotPanel(self.main_panel)
s0 = wx.BoxSizer(wx.VERTICAL)
s0.Add(self.main_panel, 1, wx.EXPAND)
self.SetSizer(s0)
self.s0 = s0
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_sizer.Add(self.plot_panel, 1, wx.EXPAND)
self.main_panel.SetSizer(self.main_sizer)
class PlotPanel(wx.Panel):
def __init__(self, parent, id = -1, dpi = None, **kwargs):
wx.Panel.__init__(self, parent, id=id, **kwargs)
self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
self.canvas = Canvas(self, -1, self.figure)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas,1,wx.EXPAND)
self.SetSizer(sizer)
sizer.SetMinSize((600, 500))
self.sizer = sizer
def test(plot_panel):
axes = plot_panel.figure.gca()
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
return legend
if __name__=="__main__":
app = wx.PySimpleApp()
frame = PlotFrame()
legend = test(frame.plot_panel)
frame.Fit()
print "legend frame pre show: ", legend.get_frame()
frame.Show(True)
print "legend frame post show:", legend.get_frame()
frame.Fit()
app.MainLoop()
Edit:
For a solution to be useful to me, I would like it to look good when the figure is automatically drawn by the program, so adjustment parameters can be hard coded in the program, or, for example, on a window resize event, but not adjusted by hand for each plot. The main things that I expect to change here are: 1) the lengths of the labels (from, say, 1 to 25 characters), 2) the windows size (usually by the user dragging around the corner, and 3) the number of points and lines. (Also, if it matters, eventually, I'll want to have dates on the bottom axis.)
I've put the legend outside of the axes so that it won't cover any data points, and I'd prefer that it stay to the right of the axes.
I'm using Python 2.6.6, wxPython 2.8.12.1, and matplotlib 1.1.0 and am stuck with these for now.
It is re-sizing correctly, you just didn't tell it to do what you want it to do.
The problem is this line:
axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
Pretty sure the bbox_to_anchor kwarg is over-ridding the loc kwarg and you are pegging the bottom left of the legend to (1.05, 0.5) in axes units. If the axes expands to fill your window, the left edge of the legend will always be 5% of the width axes to the right of the right edge of you axes, hence always out of view.
You either need to put your legend someplace else or shrink your axes (in figure fraction).
option 1 move the legend:
axes.legend(bbox_to_anchor=(0.5, 0.5)) #find a better place this is in the center
option 2 move the axes + resize the figure:
axes.set_position([.1, .1, .5, .8]) # units are in figure fraction
set_position
fig = figure()
axes = fig.add_subplot(111)
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
# adjust the figure size (in inches)
fig.set_size_inches(fig.get_size_inches() * np.array([1.5, 1]), forward=True)
# and the axes size (in figure fraction)
# to (more-or-less) preserve the aspect ratio of the original axes
# and show the legend
pos = np.array(axes.get_position().bounds)
pos[2] = .66
axes.set_position(pos)
option 3: automate option 2
fig = figure() # use plt to set this up for demo purposes
axes = fig.add_subplot(111) # add a subplot
# control paramters
left_pad = .05
right_pad = .05
# plot data
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
# set axes labels
axes.set_xlabel('test x')
axes.set_ylabel('test y')
# make the legend
legend = axes.legend(loc='center left', bbox_to_anchor=(1 + left_pad, 0.5))
# function to 'squeeze' the legend into place
def squeeze_legend(event):
fig.tight_layout()
right_frac = 1 - legend.get_window_extent().width / fig.get_window_extent().width - left_pad - right_pad
fig.subplots_adjust(right=right_frac)
fig.canvas.draw()
# call it so the first draw is right
squeeze_legend()
# use the resize event call-back to make sure it works even if the window is re-sized
fig.canvas.mpl_connect('resize_event', squeeze_legend)