Savefig doesn't respect the figure size - python-3.x

My question is very closely related to this questions and maybe to this one as well, although the solution of the latter didn't help me.
My code must generate a simple plot and save it:
def bizarre():
x = np.arange(-5,5)
y = np.arange(-10,10)
xc,yc = np.meshgrid(x,y)
zc = np.random.randint(-1,2,size=xc.shape)
plt.figure()
m = plt.pcolormesh(xc, yc, zc, shading='nearest', cmap='coolwarm',
edgecolors='gray', linewidths=1, vmin=-1, vmax=1,
in_layout=False,)
plt.colorbar(m)
ax = plt.gca()
ax.set_aspect('equal')
plt.tight_layout()
plt.savefig('bizzare.png', dpi=200)
bizarre() # saves a figure with bizarre size
The function works perfectly fine and generates something like this in a Jupyter cell:
Yet, for some mysterious reason, I get blank space on the left side of the saved figure. Something like this:
I'm genuinely interested to know why is that? Thanks.

Related

How to make an Axes transparent to events?

I am trying to find a way to make an Axes object passthrough for events.
For context, I have a figure with 6 small subplots. Each of them responds to mouse motion events by displaying a cursor dot and text info where the user aims. I also made it so that clicking a subplot will make it as large as the figure for better visibility. When moving the mouse over invisible axes, event.inaxes will still point to that ax despite being set to invisible and that is what I would like to avoid.
Below is the MRE:
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.set_gid("ax1")
ax2.set_gid("ax2")
fig.show()
def on_movement(event):
"""Write on the figure in which `Axes`object the event happened."""
width, height = fig.bbox.bounds[2:]
x = event.x/width
y = event.y/height
text = ax.get_gid() if (ax := event.inaxes) is not None else "None"
fig.texts[:] = []
fig.text(x, y, s=text, transform=fig.transFigure, c="white", bbox=dict(fc="#0055AA", ec="black"))
fig.canvas.draw()
fig.canvas.mpl_connect("motion_notify_event", on_movement)
As expected, as you hover the mouse over ax1, the empty gap and ax2, you will see one of those three texts appear:
ax1.set_position((1/3, 1/3, 2/3, 2/3))
Same thing as I arbitrarily resize and move ax1 so that it is partly overlaps with ax2.
ax2.set_visible(False)
Now this is my problem. Invisible axes still trigger events. Is there a way to make some axes "transparent" to events? Obviously the usual technique of sorting all the cases in the callback does not work here.
Currently envisaged solutions:
ideally, finding a setting akin to zorder so that the "highest" axes gets the event.
ugly workaround: set the position of the invisible axes to ((0, 0, 1e-10, 1e-10)).
less ugly: working with figure coordinates to convert event.x, event.y into event.xdata, event.ydata for the only ax that I know is visible. Basically xdata1, ydata1 = ax1.transAxes.inverted().transform((event.x, event.y)) if event.inaxes is not None + see if there are edge cases.
The latter is already implemented and does work, so save your time if you want to write a reply using that approach. I'm mostly interested in an amazing one-liner that I would have missed, something like ax2.set_silenced(True).
Python 3.8.5
Matplotlib 3.1.3
Well, setting the appropriate zorder does work actually.
ax1.set_zorder(2)
ax2.set_zorder(1)
...
def on_movement(event):
...
fig.text(x, y, ..., zorder=1000)
...

Why does x = df.index is different than x=ax.get-xticks() for twin-x plot in seaborn?

I am trying to do a plot sharing the X axis, representing time (YYYYMM) and 2 y-axis one representing a count (for bars) and the other representing the evolution of a percentage.
Desired outcome:
So I did manage to do the plot I wanted, but I am not really understanding WHY.
The following code is the one that generated the plot above
f,ax=plt.subplots(figsize=(10,6))
sns.barplot(data=df,x=df.index,y='solicitudes',color=color,label='solicitudes',ax=ax)
ax2 = ax.twinx()
sns.lineplot(data=df, x= ax.get_xticks(), y='pctMalos',ax=ax2,color='red',lw=3,label='pct')
plt.show()
On the other hand, this code
f,ax=plt.subplots(figsize=(10,6))
sns.barplot(data=df,x=df.index,y='solicitudes',color=color,label='solicitudes',ax=ax)
ax2 = ax.twinx()
sns.lineplot(data=df, x= df.index, y='pct',ax=ax2,color='red',lw=3,label='pct')
plt.show()
Generates this other image
As long as I can see the only difference is that in one I "get the x axis" and in the second one I use the same x (i.e. calling df.index, which works fine for only 1 plot)
Yet, the second one breaks.
I am also having trouble with x-ticks-labels, but I believe there is something quite basic about Seaborn that I am missing.. do you know what it is?
Thank you very much in advance to anyone who could help me!

Bar format (kind) is not displaying the right plot (matplotlib)

I need to do a plot using three variables. One of them should be on the secondary Y axis in bar format (kind), the remaining variables (two) should be on the left axis using a simple line. However, I got the following chart:
When I use the three variables in line format I get the right plot (which is not very useful for a visual analysis):
I did a quick test using a small sample from my data (code below). I get the right pic when I use bar format for the third one.
I wonder, what is going on? Is there a problem with the data size (which I dont think so bcs I get less than 100 rows)?
df2 = pd.DataFrame({'ind':[120.29, 125.45, 127.37, 130.39, 128.30],
'var1':[129.907990, 129.571185, 129.234380, 128.897574, 128.560769],
'var2':[-0.074037, -0.031806, -0.014426, 0.011578, -0.002028]})
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
df2['ind'].plot(ax=ax1)
df2['var1'].plot(ax=ax1)
df2['var2'].plot(kind='bar', ax=ax2, color='r')
plt.show()
PD: In addition, I noted that in the third pic the line is behind the bar. How can I change that?
I found the solution for this (this link helped me a lot ). Basically, it is based on the index you set up previously.
This is the new code:
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(df2.index, df2['ind'])
ax1.plot(df2.index, df2['var1'])
ax2.bar(df2.index, df2['var2'], color='r')
plt.show()
Hope this helps.

matplotlib animation video not fitting in Jupyter_Notebook Cell

Okay, so I am trying to create some animation in matplotlib. I am doing this on Jupyter-Notebook.
I am converting the animation using to_html5_video(). And displaying it using HTML(). The problem is that this video is not fitting in my cell.
fig, ax = plt.subplots()
l, = ax.plot([],[], "k.")
ax.set_xlim([0,L])
ax.set_ylim([0,L])
def animate(i):
l.set_data(xPos[:i], yPos[:i])
ani = animation.FuncAnimation(fig, animate,frames=len(xPos)).to_html5_video()
HTML(ani)
It is looking like this:
How do I fit it properly?
When I had this problem I found that I could make the video smaller by using plt.rcParams["savefig.dpi"] = 100. This gave me the notion that my config was somehow bad, so I simply deleted ~/.matplotlib/matplotlibrc and the sample code for animations now render correctly.

Captions for matshow()s in multiple-page pdf

I have been around this problem for quite a long time but I'm not able to find an answer.
So, I have a list with matrices which I want to plot (for the sake of this question I'm just having 2 random matrices:
list = [np.random.random((500, 500)), np.random.random((500, 500))]
I then want to plot each element of the list using matshow in a separate page of a pdf file:
with PdfPages('file.pdf') as pdf:
plt.rc('figure', figsize=(3,3), dpi=40)
for elem in list:
plt.matshow(elem, fignum=1)
plt.title("title")
plt.colorbar()
plt.text(0,640,"Caption")
pdf.savefig() # saves the current figure into a pdf page
plt.close()
The result is the following:
My problem is with the caption. You can see I put "Caption" in the edge of the document on purpose. This is because sometimes the actual captions I want to insert are too big to fit in one single pdf page.
So, how can I make each pdf page adjustable to the caption's content (that might vary in each page)? For example, would it be possible to set each page size to A4 or A3, and then plot/write everything in each page?
I've already tried setting up plt.figure(figsize=(X, X)) with a variable X size, but it just changes the resolution of the pdf I guess.
You may want to use the bbox_inches="tight" option when saving the file. This will adapt the figure size to its content. So it then suffices to place some text at position (0,0) in figure coordinates and align it to the top. This will then extent towards the bottom and outside the figure (so the figure when shown on screen would not contain that text), but with the bbox_inches="tight" option of savefig, the saved figure will become large enough to contain that text.
The use of the textwrap package will then also allow to limit the text in horizontal direction.
import numpy as np; np.random.seed(1)
import textwrap
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
p = np.ones(12); p[0] = 7
text2 = "".join(np.random.choice(list(" abcdefghijk"),p=p/p.sum(), size=1000))
text2 = textwrap.fill(text2, width=80)
texts = ["Caption: Example", "Caption 2: " + text2 ]
lis = [np.random.random((500, 500)), np.random.random((500, 500))]
with PdfPages('file.pdf') as pdf:
for elem,text in zip(lis,texts):
fig = plt.figure(dpi=100)
grid_size = (3,1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
fig.text(0,0, text, va="top")
plt.tight_layout()
pdf.savefig(bbox_inches="tight")
plt.close()
I think I have come up with an answer to this question myself, which solves the problem of having enough space for my text:
However, a perfect answer would be making each page's size dynamic, according to the amount of caption I put.
Anyway, my answer is the following (I essentially divided each page in a grid with 3 rows, making the upper 2 rows for the plots, and the last just for the caption) :
with PdfPages('file.pdf') as pdf:
for elem in list:
fig = plt.figure(figsize=(8.27, 11.69), dpi=100)
grid_size = (3,1)
plt.subplot2grid(grid_size, (0, 0), rowspan=2, colspan=1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
plt.subplot2grid(grid_size, (2, 0), rowspan=2, colspan=1)
plt.axis('off')
plt.text(0,1,"Caption")
plt.tight_layout()
pdf.savefig()
plt.close()
Which produces the following in each page:
Could someone find a better solution? :)

Resources