matplotlib - change figsize but keep fontsize constant - python

I want to display several figures with different sizes, making sure that the text has always the same size when the figures are printed. How can I achieve that?
As an example. Let's say I have two figures:
import matplotlib.pylab as plt
import matplotlib as mpl
mpl.rc('font', size=10)
fig1 = plt.figure(figsize = (3,1))
plt.title('This is fig1')
plt.plot(range(0,10),range(0,10))
plt.show()
mpl.rc('font', size=?)
fig2 = plt.figure(figsize = (20,10))
plt.title('This is fig2')
plt.plot(range(0,10),range(0,10))
plt.show()
How can I set the fontsize in such way that when printed the title and axis ticklabels in fig1 will have the same size as those in fig2?

In this case, the font size would be the same (i.e. also 10 points).
However, in Jupyter Notebook the figures may be displayed at a different size if they are too wide, see below:
Note that font size in points has a linear scale, so if you would want the size of the letters to be exactly twice as big, you would need to enter exactly twice the size in points (e.g. 20pt). That way, if you expect to print the second figure at 50% of the original size (length and width, not area), the fonts would be the same size.
But if the only purpose of this script is to make figures to then print, you would do best to set the size as desired (on paper or on screen), and then make the font size equal. You could then paste them in a document at that exact size or ratio and the fonts would indeed be the same size.
As noted by tcaswell, bbox_inches='tight' effectively changes the size of the saved figure, so that the size is different from what you set as figsize. As this might crop more whitespaces from some figures than others, the relative sizes of objects and fonts could end up being different for a given aspect ratio.

Related

Adjust figure height automatically, when the width is fixed and aspect ratio is defined by figure content

When using matplotlib to prepare publication-ready figures that include text, one wants to avoid re-scaling or stretching the image when including it into the (e.g. LaTeX) document. To achieve this, the standard procedure is to choose a figure width which matches the document in preparation (in LaTeX this is often the \textwidth or \columnwidth). On the other hand, it is usually less important to fix the figure height. If the figure content has a well-defined aspect ratio (for example, when plotting a grid of square images), there is an optimal figure height that maximally fills the figure, avoiding undue white space.
Ideally then, it should be possible to fix the figure width, tell matplotlib to use a constrained-layout, and let it figure out a good figure height.
Consider the following scenario:
import matplotlib.pyplot as plt
import numpy as np
ims = [np.random.random((20, 20)) for i in range(3)]
# Based on the \textwidth in our LaTeX document
width_inch = 4
# We plot the images in a 1x3 grid
fig, axes = plt.subplots(
nrows=1,
ncols=3,
layout="constrained",
linewidth=5,
edgecolor="black") # Uses the default figsize
for ax, im in zip(axes.flatten(), ims):
ax.imshow(im)
# Fix the figure width
fig.set_figwidth(width_inch)
This produces:
Clearly the figure height is much too large. On the other hand, making it too small leaves too much whitespace:
fig, axes = plt.subplots(
nrows=1,
ncols=3,
layout="constrained",
linewidth=5,
edgecolor="black",
figsize=(width_inch, 1))
Is it possible to get matplotlib to calculate and apply an optimal figure height, or does this need to be manually adjusted?

How to define the aspect ratio of a matplotlib figure?

Python produces an aspect ratio that is suitable for its content e.g., respects the structure of the font of each label, axis title, etc. This is the basic code using Jupyter Notebook:
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
plt.show()
However, I want to save the image as a tiff, with a dpi of 600, and a width of 8.3cm (maximum height is an A4 page, but the nature of my question will make that irrelevant).
I'm using the code:
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
This produces the following:
All good so far. Next, the Royal Soc. of Chemistry expect a single column image to be 8.3 cm in width (height, no more than the page).
My question:
Is there any way for Python to calculate the height of the figure given only the wdith, whilst maintaining the correct aspect ratio for the fonts, titles and ticks etc.? If I specify width=height, the image looks terrible:
fig.set_size_inches(3.26,3.26)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
Or is this a case where I define the size of the figure first, then adjust the font sizes as a separate step? I'm looking more for a one-fix solution as I have multiple figures of different size requirements (all being dpi=600 though) to produce.
Here you go:
dataLipid = np.random.uniform(0,1,(100,5)) * 90000
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
fig.set_size_inches(3.26,3.26)
# rotate ticks
plt.xticks(rotation=45)
# set bottom margin
plt.subplots_adjust(left=0.2, bottom=0.3)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
There is no general solution as far as I know. So setting the correct margin depends on your content and your data. Rotating the ticks is always a good option to make them readable in case of close spacing.
You can use the Axes.set_aspect method.
# square plot
ax.set_aspect(1)
Also have a look at the tight_layout method to ensure everything is redrawn to fit in the figure.

Is it possible to automatically scale the figure size, but keep the plot size constant in matplotlib?

I am using matplotlib to create multiple bar plots using the following code:
fig = plt.figure(figsize=(4, 4))
plt.barh(y=y, width=width, height=0.5)
plt.yticks(y, labels)
plt.xlabel("Contribution")
plt.tight_layout()
plt.show()
Since the length of my y-ticks labels can vary, the plot can get squeezed together as in the case below:
In other cases the plot looks fine:
Now, I was wondering, if there is an option in matplotlib to keep the plot size constant, but scale the figure size automatically (in horizontal direction)? My goal is that the plot size stays always the same, independent of the y-label length (because they vary inbetween plots). Thank you!

Set a minimum tile size for seaborn heatmap

Using seaborn and matplotlib.pyplot in python is there a way to enforce a minimum/maximum size for the tiles in a heatmap?
I want to show the annotated values inside the tiles, but for more than a 9-10 samples the tiles become too small for the text. I dont want to set a fixed larger figure size to increase the tile size, since its not a one-time graph and I dont know how many samples will need to be displayed each time.
#graph plotting part
rcParams.update({"figure.constrained_layout.use": True})
ax = seaborn.heatmap(plotdata, cmap=seaborn.color_palette("flare", as_cmap=True), annot=True, linewidths=.5, fmt=".2f")
plt.show(ax)
Leads to:

Maintain pixel-size and aspect ratio of elements in matplotlib

After a few years of finding solutions to all my coding-problems on this site, this is my first post with (as far as I can tell) a new question!
I want to create several bar-charts from one data-set and save them as individual images. I want the image-size to scale automatically so that any given object (e.g. a 1x1 square) appears the same size on every image.
The following code produces two such images in which each 1x1 element is about 60x60 pixel, so far so good:
import matplotlib.pyplot as plt
def barchart(bars,size,title):
hspace,vspace = (max(size)+1,len(size))
fig = plt.figure(figsize=(hspace,vspace+1))
fig.add_axes([0.2,0.2,0.6,0.6])
plt.title(title)
plt.axis('scaled')
x_pos = xrange(vspace)
plt.xlim(0,hspace)
plt.ylim(-1,vspace)
plt.barh(x_pos, size, height=1, align='center', alpha=0.5)
plt.yticks(x_pos, bars)
plt.savefig(title+'.png',bbox_inches='tight')
plt.clf()
barchart(["1x1","A","B","C"],[1,3,5,2],"many short bars")
barchart(["1x1","A"],[1,17],"few long bars")
But I would like to do this with a different aspect-ratio, so that e.g. each 1x1 element appears as 60x30 pixel on the image. Is there a replacement for .axis('scaled') which does this? I have tried to scale the width in figsize, xlim and both, as well as in .add_axes() and several key-words in .axis(). They all seem to affect the final scale and aspect ratio of the images in different ways.
The exact pixel-size does not matter, whether it is 60x30 or 66x33 or otherwise, as long as it is consistent throughout all images.
Finally figured out the answer with the hints in the comment above and some more trial-and-error:
import matplotlib.pyplot as plt
def barchart(bars,size,title):
hspace,vspace = (max(size)+1,len(size))
AR = 0.5 # x-axis will be scaled to 50%
fig = plt.figure(figsize=(AR*hspace,vspace+1))
fig.add_axes([0.2,0.2,0.6,0.6])
plt.xlim(0,hspace)
plt.ylim(-1,vspace)
plt.title(title)
x_pos = xrange(vspace)
plt.barh(x_pos, size, height=1, align='center', alpha=0.5)
plt.yticks(x_pos, bars)
plt.savefig(title+'.png',bbox_inches='tight')
plt.clf()
barchart(["1x1","A","B","C"],[1,3,5,2],"many short bars")
barchart(["1x1","A"],[1,17],"few long bars")
The solution was to fix both the figure size and the axis limits to the same proportions and to simply leave out the .axis('scaled'). Then scale only the fig-width by the desired factor.

Categories

Resources