How to shift the colorbar position to right in matplotlib? - matplotlib

I draw a scatter chart as below :
The code is :
sc = plt.scatter(x, y, marker='o', s=size_r, c=clr, vmin=lb, vmax=ub, cmap=mycm, alpha=0.65)
cbar = plt.colorbar(sc, shrink=0.9)
And I want to shift the colorbar to right a little bit to extend the drawing area. How to do that ?

Use the pad attribute.
cbar = plt.colorbar(sc, shrink=0.9, pad = 0.05)
The documentation of make_axes() describes how to use pad: "pad: 0.05 if vertical, 0.15 if horizontal; fraction of original axes between colorbar and new image axes".

Actually you can put the colorbar anywhere you want.
fig1=figure()
sc = plt.scatter(x, y, marker='o', s=size_r, c=clr, vmin=lb, vmax=ub, cmap=mycm, alpha=0.65)
position=fig1.add_axes([0.93,0.1,0.02,0.35]) ## the parameters are the specified position you set
fig1.colorbar(sc,cax=position) ##

Related

How to change matplotlib pcolor colobar tick font size?

I try to change the font size of the ticks on colorbar. It is not the x or y axis ticks; but, the ticks of the "color bar" appeared on the right side of the plt.pcolor plot.
I searched online, and found a couple of suggestions, e.g.,
plt.figure(figsize=(8, 6), dpi=150)
cbar = plt.pcolor(x, y, z, cmap='jet')
cbar.ax.set_yticklabels(cbar.ax.get_yticklabels(), fontsize=20)
But, it does not change the tick font size of the colorbar.
I appreciate any suggestions.
Exactly how you would do it for a normal axis (at least for 3.4.3)
fig, ax = plt.subplots()
pc = ax.pcolormesh(np.random.randn(20, 20))
cb = fig.colorbar(pc)
cb.ax.tick_params(axis='y', which='major', labelsize=16)
plt.show()

How to place matplotlib legend according to coordinate

I have several plots and one of these showed below:
Example plot
Problem is I have many plots and I need to put the legend differently according to the position where x=0 and line of x=0 may vary in different plots.
How can I achieve this?
besides, bbox_to_anchor just allow me locate relatively to the fig, but have no idea of the inside (x,y) coordinate.
This is the part plotting:
ax.errorbar(x=x, y=y_erd, yerr=e_erd, fmt='-o',ecolor='orange',elinewidth=1,ms=5,mfc='wheat',mec='salmon',capsize=3)
ax.errorbar(x=x, y=y_ers, yerr=e_ers, fmt='-o',ecolor='blue',elinewidth=1,ms=5,mfc='wheat',mec='salmon',capsize=3)
ax.legend(['ERD', 'ERS'], loc="upper left", bbox_to_anchor=(1, 0.85),fontsize='x-small')
ax.axhline(y=0, color='r', linestyle='--')
We have created a code to calculate the zero position of the x and y axes using a simple sample as an example. First, get the tick values for each axis. Then, use the obtained value to get the index of zero. The next step is to calculate the position of the tick marks for the difference between the minimum and maximum values. From the array, we obtain the coordinates based on the zero index we obtained earlier. Set the obtained coordinates to bbox_to_anchor=[].
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 500)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, label='x=0,y=0')
xticks, yticks = ax.get_xticks(), ax.get_yticks()
xpos, ypos = 0, 0
for i,(x,y) in enumerate(zip(xticks, yticks)):
if x == 0:
xpos = i
if y == 0:
ypos = i
print(xpos, ypos)
x_min, x_max = ax.get_xlim()
xticks = [(tick - x_min)/(x_max - x_min) for tick in xticks]
y_min, y_max = ax.get_ylim()
yticks = [(tick - y_min)/(y_max - y_min) for tick in yticks]
print(xticks[xpos], yticks[ypos])
ax.legend(bbox_to_anchor=[xticks[xpos], yticks[ypos]], loc='center')
plt.show()

matplotlit colorbar title hangs outside figure

What is the best way to specify my colorbar legend location while ensuring the legend title is within the figure? Sometimes the location will be upper right, as shown here; but in other plots it will be variable, upper/lower left/right.
It is okay if the solution doesn't use inset_axes().
Alternative Solution:
It would also be okay if the colorbar legend is to the right of the subplot, if the "My Legend" title is vertical and on the left, and the tick labels are on the right and horizontal (I don't know how to do that).
Using Python 3.8.
## Second Plot
vals2 = ax2.scatter(df.x, df.y, edgecolors = 'none', c = df.z,
norm = mcolors.LogNorm(), cmap=rainbow)
ax2.set_aspect('equal')
ax2.set_title('Subplot Title', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('y');
cbaxes = inset_axes(ax2, width="30%", height="10%", location = 'upper right')
clb = plt.colorbar(vals2, cax=cbaxes, format = '%1.2f', orientation='horizontal');
clb.ax.set_title('My Legend')
I would still prefer to have the colorbar (with tick labels and title) inside the subplot; but I did find a way to do the Alternative Solution:
vals2 = ax2.scatter(df.x, df.y, edgecolors = 'none', c = df.z,
norm = mcolors.LogNorm(), cmap=rainbow)
ax2.set_aspect('equal')
ax2.set_title('Subplot Title', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('y');
clb = fig.colorbar(slips2, ax=ax2, format = '%1.2g', location='right', aspect=25)
clb.ax.set_ylabel('My Legend')
clb.ax.yaxis.set_label_position('left')
The color bar is taller than the subplot because ax2 is constrained to be equal xy aspect ratio based on the limits in another subplot (ax1, not shown).

Creating figure with exact size and no padding (and legend outside the axes)

I am trying to make some figures for a scientific article, so I want my figures to have a specific size. I also see that Matplotlib by default adds a lot of padding on the border of the figures, which I don't need (since the figures will be on a white background anyway).
To set a specific figure size I simply use plt.figure(figsize = [w, h]), and I add the argument tight_layout = {'pad': 0} to remove the padding. This works perfectly, and even works if I add a title, y/x-labels etc. Example:
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
plt.savefig('figure01.pdf')
This creates a pdf file with exact size 3x2 (inches).
The issue I have is that when I for example add a text box outside the axis (typically a legend box), Matplotlib does not make room for the text box like it does when adding titles/axis labels. Typically the text box is cut off, or does not show in the saved figure at all. Example:
plt.close('all')
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure02.pdf')
A solution I found elsewhere on SO was to add the argument bbox_inches = 'tight' to the savefig command. The text box is now included like I wanted, but the pdf is now the wrong size. It seems like Matplotlib just makes the figure bigger, instead of reducing the size of the axes like it does when adding titles and x/y-labels.
Example:
plt.close('all')
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure03.pdf', bbox_inches = 'tight')
(This figure is 3.307x2.248)
Is there any solution to this that covers most cases with a legend just outside the axes?
So the requirements are:
Having a fixed, predefined figure size
Adding a text label or legend outside the axes
Axes and text cannot overlap
The axes, together with the title and axis labels, sits tightly agains the figure border.
So tight_layout with pad = 0, solves 1. and 4. but contradicts 2.
One could think on setting pad to a larger value. This would solve 2. However, since it's is symmetric in all directions, it would contradict 4.
Using bbox_inches = 'tight' changes the figure size. Contradicts 1.
So I think there is no generic solution to this problem.
Something I can come up with is the following: It sets the text in figure coordinates and then resizes the axes either in horizontal or in vertical direction such that there is no overlap between the axes and the text.
import matplotlib.pyplot as plt
import matplotlib.transforms
fig = plt.figure(figsize = [3,2])
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
def text_legend(ax, x0, y0, text, direction = "v", padpoints = 3, margin=1.,**kwargs):
ha = kwargs.pop("ha", "right")
va = kwargs.pop("va", "top")
t = ax.figure.text(x0, y0, text, ha=ha, va=va, **kwargs)
otrans = ax.figure.transFigure
plt.tight_layout(pad=0)
ax.figure.canvas.draw()
plt.tight_layout(pad=0)
offs = t._bbox_patch.get_boxstyle().pad * t.get_size() + margin # adding 1pt
trans = otrans + \
matplotlib.transforms.ScaledTranslation(-offs/72.,-offs/72.,fig.dpi_scale_trans)
t.set_transform(trans)
ax.figure.canvas.draw()
ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
trans2 = matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans) + \
ax.figure.transFigure.inverted()
tbox = trans2.transform(t._bbox_patch.get_window_extent())
bbox = ax.get_position()
if direction=="v":
ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox[0][1]-bbox.y0])
else:
ax.set_position([bbox.x0, bbox.y0,tbox[0][0]-bbox.x0, bbox.height])
# case 1: place text label at top right corner of figure (1,1). Adjust axes height.
#text_legend(ax, 1,1, 'my text here', bbox = dict(boxstyle = 'round'), )
# case 2: place text left of axes, (1, y), direction=="v"
text_legend(ax, 1., 0.8, 'my text here', margin=2., direction="h", bbox = dict(boxstyle = 'round') )
plt.savefig(__file__+'.pdf')
plt.show()
case 1 (left) and case 2 (right):
Doin the same with a legend is slightly easier, because we can directly use the bbox_to_anchor argument and don't need to control the fancy box around the legend.
import matplotlib.pyplot as plt
import matplotlib.transforms
fig = plt.figure(figsize = [3.5,2])
ax = fig.add_subplot(111)
ax.set_title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
ax.plot([1,2,3], marker="o", label="quantity 1")
ax.plot([2,1.7,1.2], marker="s", label="quantity 2")
def legend(ax, x0=1,y0=1, direction = "v", padpoints = 3,**kwargs):
otrans = ax.figure.transFigure
t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs)
plt.tight_layout(pad=0)
ax.figure.canvas.draw()
plt.tight_layout(pad=0)
ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\
ax.figure.transFigure.inverted()
tbox = t.get_window_extent().transformed(trans2 )
bbox = ax.get_position()
if direction=="v":
ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0])
else:
ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height])
# case 1: place text label at top right corner of figure (1,1). Adjust axes height.
#legend(ax, borderaxespad=0)
# case 2: place text left of axes, (1, y), direction=="h"
legend(ax,y0=0.8, direction="h", borderaxespad=0.2)
plt.savefig(__file__+'.pdf')
plt.show()
Why 72? The 72 is the number of points per inch (ppi). This is a fixed typographic unit e.g. fontsizes are always given in points (like 12pt). Because matplotlib defines the padding of the text box in units relative to fontsize, which is points, we need to use 72 to transform back to inches (and then to display coordinates). The default dots per inch (dpi) is not touched here, but is accounted for in fig.dpi_scale_trans. If you want to change dpi you need to make sure the figure dpi is set when creating the figure as well as when saving it (use dpi=.. in the call to plt.figure() as well as plt.savefig()).
As of matplotlib==3.1.3, you can use constrained_layout=True to achieve the desired result. This is currently experimental, but see the docs for a very helpful guide (and the section specifically on legends). Note that the legend will steal space from the plot, but this is unavoidable. I've found that as long as the legend does not take up too much space relative to the size of the plot, then the figure gets saved without cropping anything.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(3, 2), constrained_layout=True)
ax.set_title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
ax.plot([0,1], [0,1], label='my text here')
ax.legend(loc='center left', bbox_to_anchor=(1.1, 0.5))
fig.savefig('figure03.pdf')

vertical & horizontal lines in matplotlib

I do not quite understand why I am unable to create horizontal and vertical lines at specified limits. I would like to bound the data by this box. However, the sides do not seem to comply with my instructions. Why is this?
# CREATING A BOUNDING BOX
# BOTTOM HORIZONTAL
plt.axhline(y=.4, xmin=0.25, xmax=0.402, linewidth=2, color = 'k')
# RIGHT VERTICAL
plt.axvline(x=0.402, ymin=0.4, ymax = 0.615, linewidth=2, color='k')
# LEFT VERTICAL
plt.axvline(x=0.1, ymin=0.58, ymax = 0.79, linewidth=2, color='k')
plt.show()
The pyplot functions you are calling, axhline() and axvline() draw lines that span a portion of the axis range, regardless of coordinates. The parameters xmin or ymin use value 0.0 as the minimum of the axis and 1.0 as the maximum of the axis.
Instead, use plt.plot((x1, x2), (y1, y2), 'k-') to draw a line from the point (x1, y1) to the point (x2, y2) in color k. See pyplot.plot.
This may be a common problem for new users of Matplotlib to draw vertical and horizontal lines. In order to understand this problem, you should be aware that different coordinate systems exist in Matplotlib.
The method axhline and axvline are used to draw lines at the axes coordinate. In this coordinate system, coordinate for the bottom left point is (0,0), while the coordinate for the top right point is (1,1), regardless of the data range of your plot. Both the parameter xmin and xmax are in the range [0,1].
On the other hand, method hlines and vlines are used to draw lines at the data coordinate. The range for xmin and xmax are the in the range of data limit of x axis.
Let's take a concrete example,
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 5, 100)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.axhline(y=0.5, xmin=0.0, xmax=1.0, color='r')
ax.hlines(y=0.6, xmin=0.0, xmax=1.0, color='b')
plt.show()
It will produce the following plot:
The value for xmin and xmax are the same for the axhline and hlines method. But the length of produced line is different.
If you want to add a bounding box, use a rectangle:
ax = plt.gca()
r = matplotlib.patches.Rectangle((.5, .5), .25, .1, fill=False)
ax.add_artist(r)
Rectangle doc