Matplotlib tight_layout set rect tuple - python

I have created the following code, which prints a plot and formats the axis labels and ticks in a way that is useful to me. I have a problem with tight_layout, which leaves my vertically rotated x-axis tick labels as well as the x-axis label outside the figure window.
To try and solve the problem, what I did was to manually rescale the plot window, and set the rect tuple manually from the figure window. After some tries, I found that the optimal values for (left, bottom, right, top) in my case were [0.163, 0.391, 0.905, 0.977]. Next, I thought I should incorporate that to my code, so that my plots emerge with correct sizing in the first place: To that end, I used the command:
fig.tight_layout(rect=[0.163, 0.391, 0.905, 0.977])
However, it did not work, and the figure emerges with different rect values than the ones I set.
Question 1: How can I set the rect values from my code, rather than setting them manually?
Question 2: Is there a better/easier alternative to achieve the desired functionality?
# dt_objects is a list of datetime objects. My x-axis is timestamps
# for y_axis, set any series. The code will set the y axis based on the min,max value of y-values
matdates=date2num(dt_objects)
x_axis=matdates
fig,ax = plt.subplots()
ax.plot_date(x_axis,y_axis,markersize=8)
ax.axhline(y=y_axis.mean(),linestyle='--',color='red',alpha=0.5)
ax.xaxis.set_major_locator(AutoDateLocator(minticks=1, maxticks=5)) #Set position of Major X ticks
ax.xaxis.set_minor_locator(AutoDateLocator(minticks=10, maxticks=30)) #Set position of Minor X ticks
ax.xaxis.set_major_formatter( DateFormatter('%Y/%m/%d-%H:%M:%S')) #Set format of Major X ticks
ax.xaxis.set_minor_formatter( DateFormatter('%H:%M:%S')) #Set format of X ticks
ax.tick_params(axis='x',which='major',rotation=90,labelsize=14) #Set parameters of Major X ticks
ax.tick_params(axis='x',which='minor',rotation=80,labelsize=12) #Set parameters of Major X ticks
plt.setp(ax.get_xticklabels(), fontsize=14, fontweight="bold") #Set font of Major X ticks
ax.yaxis.set_major_formatter(FormatStrFormatter('%.2f')) #Set format of Major Y ticks
ax.tick_params(axis='y',which='major',labelsize=14)
calculateYpadding=(y_axis.max()-y_axis.min())*0.1 # Padding is 10% of the max difference in y values :)
ax.set_ylim(round(y_axis.min(),2)-calculateYpadding, round(y_axis.max(),2)+calculateYpadding) #Set boundaries of Y axis
ax.yaxis.set_major_locator(MaxNLocator(nbins = 'auto',min_n_ticks = 5))
plt.grid()
ax.set_xlabel("Time",style='italic',fontsize=14)
ax.xaxis.set_label_coords(1.08, -0.1)
ax.set_ylabel(str(MeasurementType),labelpad=10,style='italic', fontsize=14)
#ax.yaxis.set_label_coords(-0.1, 0.5)
#plt.xlabel("Time",horizontalalignment='right', position=(1,60))\
#ax.set_title(str(MeasurementType),fontweight="bold", pad=20,fontsize=20)
rstButton.config(state=tk.NORMAL)
fig.tight_layout(rect=[0.163, 0.391, 0.905, 0.977])
plt.show()
EDIT: Since I was told my question is not clear, I am including two screenshots to better explain the problem. Here is the result of the above-mentioned code. Also, on the bottom left, you can see on the window that top, bottom, left, right have different values than the ones set at rect tuple in my code.
My desired output is this:
It is achieved by manually tweaking the parameters of the figure, until it reaches a point that is satisfactory. It is from here that i extracted the values and placed them in the rect tuple, but it did not work. Hopefully it is clearer now what I want to achieve, and what the problem is.
EDIT 2: Here are the results of the suggested solution
fig,ax = plt.subplots(constrained_layout=True)
As you can see, the labels of both axles are not correctly placed.

Try:
fig, ax = plt.subplots(constrained_layout=True)

Related

How to automatically get 'nice' values for first and last axis labels in matplotlib?

In this plot, matplotlib automatically hides the first and last axis labels. However, the desired behavior is to always show both the first and last axis labels and that too as a 'nice' value. By 'nice' value, I mean a major tic should be present at both boundaries of the axis. For example, in the figure shown below, the x-axis would have started from -0.1 and ended at 1.5. Similarly, the y-axis would have started from -0.25 and ended at 2.00. How can this be achieved in Matplotlib?
Thanks in advance for your help.
I solved this problem by first letting matplotlib find the ideal tick locations and then setting the axis limits such that one additional tick is added on both the edges of the axis.
plt.figure()
plt.plot(xdata, data)
loc, labels = plt.xticks() #returns the current tics and lables.
min_new = loc[0] - (loc[1]-loc[0])
max_new = loc[len(loc)-1] + (loc[1]-loc[0])
plt.xlim(left=min_new, right=max_new)
plt.gca().xaxis.set_major_locator(AutoLocator())
plt.gca().yaxis.set_major_locator(AutoLocator())
plt.show()
Edit:
The same could also be achieved by extending axis limits to the hidden ticks at the edges. i.e.,
loc, labels = plt.xticks()
plt.xlim(left=loc[0], right=loc[len(loc)-1])

How to define ylabel position relative to axis with matplotlib?

I need to precisely control the position of my ylabel independently of my yticklabels with matplotlib. This is because I have a matplotlib animation that currently has the ylabel jumping around as I change yticklabels. This is undesirable.
The docs seem to only allow me to specify distance from the leftmost part of my yticklabels. (which does not solve the problem, and indeed is causing it)
One solution would be to manually put the label. But is there a simpler way?
You can emulate the behavior of a normal y-label by adding text explicitly to the axes. If the y-limits are changing quite a bit, this is best done by placing the text in axes coordinates, rather than data coordinates. This is done with the transform keyword-argument, like so:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
t = ax.text(-0.1, 0.5, 'Y label', rotation=90,
verticalalignment='center', horizontalalignment='right',
transform=ax.transAxes)
ax.set_ylim(-10, 10) # Change y-limits, label position won't change.
This places the text halfway up the axes, and slightly to the left. Changes to the data limits of the axes have no effect on the text, as it is always defined in axes coordinates. Similarly, scaling the plot or axes (resizing the window with the mouse, using fig.set_size_inches, etc) will keep the y-label in position relative to the axes box itself, exactly what you want for a label.
You may have to play with the x-position of the label, to make sure it doesn't overlap the tickmarks as they change during animation.

Adjusting the position of an xticklabel in matplotlib has no effect in x-direction

Using matplotlib 2.2.2 with gridspec in Python 3.6.5, I created a huge plot for a research paper with several subplots. The axes objects are stored in a dictionary called axes. This dictionary is passed to the function adjust_xticklabels(), which is supposed to align the first xticklabel slightly to the right and the last xticklabel slightly to the left in each subplot, such that the xticklabels of neighbouring plots dont get in the way of each other. The function is defined as:
def adjust_xticklabels(axes, rate = 0.1):
for ax in axes.values():
left, right = ax.get_xlim() # get boundaries
dist = right-left # get distance
xtl = ax.get_xticklabels()
if len(xtl) > 1:
xtl[0].set_position((left + rate*dist, 0.)) # (x, y), shift right
xtl[-1].set_position((right - rate*dist, 0.)) # shift left
Calling it has no effect. Of course I also tried it with ridiculously high values. However, is has an effect in y-direction, for instance in case of setting xtl[0].set_position((0.3, 0.3)).
A simple reproduction:
ax = plt.subplot(111)
ax.plot(np.arange(10))
xtl = ax.get_xticklabels()
xtl[4].set_position((0.3, 0.3)) # wlog, 4 corresponds to 6
I spent quite a while on trying to figure out if this is a feature or a bug. Did I miss something or is this a bug? Is there any other way to do the same thing?
This is a feature, no bug. The ticklabels are positionned at drawtime to sit at the correct locations according to the ticker in use. This ensures that the label always sits where the corresponding tick is located. If you change the limits, move or zoom the plot, the label always follows those changes.
You are usually not meant to change this location, but you may, by adding a custom transform to it. This is described in
Moving matplotlib xticklabels by pixel value. The general idea is to set a translating transformation on the label. E.g. to translate the second label by 20 pixels to the right,
import matplotlib.transforms as mtrans
# ...
trans = mtrans.Affine2D().translate(20, 0)
label = ax.get_xticklabels()[1]
label.set_transform(label.get_transform()+trans)

How to enforce both xlim and ylim while using ax.axis('equal')?

I want to use ax.axis('equal') to force even spacing on X & Y, but I also want to prescribe specific ranges for the X and Y axes. If the margins are also fixed, the problem is over constrained and the result is shown on the left side of the Figure 1. If instead, the margins were allowed to automatically increase themselves to take up the slack, then xlim and ylim could stay as I set them while still satisfying axis('equal'). An example of what I'm after is shown on the right side of Figure 1. How can I allow the plot margins to "float"?
f,ax=plt.subplots(1) #open a figure
ax.axis('equal') #make the axes have equal spacing
ax.plot([0,20],[0,20]) #test data set
#change the plot axis limits
ax.set_xlim([2,18])
ax.set_ylim([5,15])
#read the plot axis limits
xlim2=array(ax.get_xlim())
ylim2=array(ax.get_ylim())
#define indices for drawing a rectangle with xlim2, ylim2
sqx=array([0,1,1,0,0])
sqy=array([0,0,1,1,0])
#plot a thick rectangle marking the xlim2, ylim2
ax.plot(xlim2[sqx],ylim2[sqy],lw=3) #this does not go all the way around the edge
Figure 1: output from the above code snippet.
ax.set_aspect('equal',adjustable='box')

Change distance between boxplots in the same figure in python [duplicate]

I'm drawing the bloxplot shown below using python and matplotlib. Is there any way I can reduce the distance between the two boxplots on the X axis?
This is the code that I'm using to get the figure above:
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['ytick.direction'] = 'out'
rcParams['xtick.direction'] = 'out'
fig = plt.figure()
xlabels = ["CG", "EG"]
ax = fig.add_subplot(111)
ax.boxplot([values_cg, values_eg])
ax.set_xticks(np.arange(len(xlabels))+1)
ax.set_xticklabels(xlabels, rotation=45, ha='right')
fig.subplots_adjust(bottom=0.3)
ylabels = yticks = np.linspace(0, 20, 5)
ax.set_yticks(yticks)
ax.set_yticklabels(ylabels)
ax.tick_params(axis='x', pad=10)
ax.tick_params(axis='y', pad=10)
plt.savefig(os.path.join(output_dir, "output.pdf"))
And this is an example closer to what I'd like to get visually (although I wouldn't mind if the boxplots were even a bit closer to each other):
You can either change the aspect ratio of plot or use the widths kwarg (doc) as such:
ax.boxplot([values_cg, values_eg], widths=1)
to make the boxes wider.
Try changing the aspect ratio using
ax.set_aspect(1.5) # or some other float
The larger then number, the narrower (and taller) the plot should be:
a circle will be stretched such that the height is num times the width. aspect=1 is the same as aspect=’equal’.
http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_aspect
When your code writes:
ax.set_xticks(np.arange(len(xlabels))+1)
You're putting the first box plot on 0 and the second one on 1 (event though you change the tick labels afterwards), just like in the second, "wanted" example you gave they are set on 1,2,3.
So i think an alternative solution would be to play with the xticks position and the xlim of the plot.
for example using
ax.set_xlim(-1.5,2.5)
would place them closer.
positions : array-like, optional
Sets the positions of the boxes. The ticks and limits are automatically set to match the positions. Defaults to range(1, N+1) where N is the number of boxes to be drawn.
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.boxplot.html
This should do the job!
As #Stevie mentioned, you can use the positions kwarg (doc) to manually set the x-coordinates of the boxes:
ax.boxplot([values_cg, values_eg], positions=[1, 1.3])

Categories

Resources