Matplotlib - maintain plot size of uneven subplots - python-3.x

I've been creating uneven subplots in matplotlib based on this question. The gridspec solution (third answer) worked a little better for me as it gives a bit more flexibility for the exact sizes of the subplots.
When I add a plot of a 2D array with imshow() the affected subplot is resized to the shape of the array. Is there any way to avoid that and keep the subplot-sizes (or rather aspect-ratio) fixed?
Here's the example code and the resulting image with the subplot-sizes I'm happy with:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot
fig = plt.figure(figsize=(12, 9))
gs = gridspec.GridSpec(20, 20)
ax1 = fig.add_subplot(gs[0:5,0:11])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[6:11,0:11])
ax2.plot(y, x)
ax3 = fig.add_subplot(gs[12:20,0:11])
ax3.plot(y, x)
ax4 = fig.add_subplot(gs[0:9,13:20])
ax4.plot(x, y)
ax5 = fig.add_subplot(gs[11:20,13:20])
ax5.plot(y, x)
plt.show()
This is what happens if I additionally plot data from a 2D array with the following lines (insert before plt.show):
2Ddata = np.arange(0, 10, 0.1).reshape(10, 10)
im = ax3.imshow(2Ddata, cmap='rainbow')
How can I restore the original size of the subplot from ax3 (lower left corner)?

Including the line ax3.set_aspect('auto') seems to have solved the issue.

Related

How to get the plot of 3D geometry with equal axes in python using matplotlib?

I am facing a problem to plot the geometry in the python using matplotlib. I would like to have a plot which can have the equal lenth in all three axes (X, Y, Z). I have written below code but it does not show any equal axes in the obtained geometry.
How can I get the plot with equal axes?
def plotting(x, y, z, figname):
fig = plt.figure(figsize = (50,50))
ax = plt.axes(projection='3d')
ax.grid()
ax.scatter(x, y, z, c = 'r', s = 50)
ax.set_title(figname)
ax.set_xlabel('x', labelpad=20)
ax.set_ylabel('y', labelpad=20)
ax.set_zlabel('z', labelpad=20)
Matplotlib makes this very difficult. One way you could "achieve" that is by setting the same limits to xlim, ylim, zlim:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
t = np.random.uniform(0, 2*np.pi, n)
p = np.random.uniform(0, 2*np.pi, n)
x = (4 + np.cos(t)) * np.cos(p)
y = (1.5 + np.cos(t)) * np.sin(p)
z = np.sin(t)
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.scatter(x, y, z)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.set_zlim(-4, 4)
plt.show()
Otherwise, your best bet is to use a different plotting library for 3D plots. Plotly allows to easily set equal aspect ratio. K3D-Jupyter and Mayavi uses equal aspect ratio by default.

Change colorbar limits without changing the values of the data it represents in scatter

I'm trying to change a colorbar attached to a scatter plot so that the minimum and maximum of the colorbar are the minimum and maximum of the data, but I want the data to be centred at zero as I'm using a colormap with white at zero. Here is my example
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1, 61)
y = np.linspace(0, 1, 61)
C = np.linspace(-10, 50, 61)
M = np.abs(C).max() # used for vmin and vmax
fig, ax = plt.subplots(1, 1, figsize=(5,3), dpi=150)
sc=ax.scatter(x, y, c=C, marker='o', edgecolor='k', vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
cbar=fig.colorbar(sc, ax=ax, label='$R - R_0$ (mm)')
ax.set_xlabel('x')
ax.set_ylabel('y')
As you can see from the attached figure, the colorbar goes down to -M, where as I want the bar to just go down to -10, but if I let vmin=-10 then the colorbar won't be zerod at white. Normally, setting vmin to +/- M when using contourf the colorbar automatically sorts to how I want. This sort of behaviour is what I expect when contourf uses levels=np.linspace(-M,M,61) rather than setting it with vmin and vmax with levels=62. An example showing the default contourf colorbar behaviour I want in my scatter example is shown below
plt.figure(figsize=(6,5), dpi=150)
plt.contourf(x, x, np.reshape(np.linspace(-10, 50, 61*61), (61,61)),
levels=62, vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
plt.colorbar(label='$R - R_0$ (mm)')
Does anyone have any thoughts? I found this link which I thought might solve the problem, but when executing the cbar.outline.set_ydata line I get this error AttributeError: 'Polygon' object has no attribute 'set_ydata' .
EDIT a little annoyed that someone has closed this question without allowing me to clarify any questions they might have, as none of the proposed solutions are what I'm asking for.
As for Normalize.TwoSlopeNorm, I do not want to rescale the smaller negative side to use the entire colormap range, I just want the colorbar attached to the side of my graph to stop at -10.
This link also does not solve my issue, as it's the TwoSlopeNorm solution again.
After changing the ylim of the colorbar, the rectangle formed by the surrounding spines is too large. You can make this outline invisible. And then add a new rectangular border:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1, 61)
y = np.linspace(0, 1, 61)
C = np.linspace(-10, 50, 61)
M = np.abs(C).max() # used for vmin and vmax
fig, ax = plt.subplots(1, 1, figsize=(5, 3), dpi=150)
sc = ax.scatter(x, y, c=C, marker='o', edgecolor='k', vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
cbar = fig.colorbar(sc, ax=ax, label='$R - R_0$ (mm)')
cb_ymin = C.min()
cb_ymax = C.max()
cb_xmin, cb_xmax = cbar.ax.get_xlim()
cbar.ax.set_ylim(cb_ymin, cb_ymax)
cbar.outline.set_visible(False) # hide the surrounding spines, which are too large after set_ylim
cbar.ax.add_patch(plt.Rectangle((cb_xmin, cb_ymin), cb_xmax - cb_xmin, cb_ymax - cb_ymin,
fc='none', ec='black', clip_on=False))
plt.show()
Another approach until v3.5 is released is to make a custom colormap that does what you want (see also https://matplotlib.org/stable/tutorials/colors/colormap-manipulation.html#sphx-glr-tutorials-colors-colormap-manipulation-py)
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
from matplotlib.colors import ListedColormap
fig, axs = plt.subplots(2, 1)
X = np.random.randn(32, 32) + 2
pc = axs[0].pcolormesh(X, vmin=-6, vmax=6, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[0])
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
from matplotlib.colors import ListedColormap
fig, axs = plt.subplots(2, 1)
X = np.random.randn(32, 32) + 2
pc = axs[0].pcolormesh(X, vmin=-6, vmax=6, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[0])
def keep_center_colormap(vmin, vmax, center=0):
vmin = vmin - center
vmax = vmax - center
dv = max(-vmin, vmax) * 2
N = int(256 * dv / (vmax-vmin))
RdBu_r = cm.get_cmap('RdBu_r', N)
newcolors = RdBu_r(np.linspace(0, 1, N))
beg = int((dv / 2 + vmin)*N / dv)
end = N - int((dv / 2 - vmax)*N / dv)
newmap = ListedColormap(newcolors[beg:end])
return newmap
newmap = keep_center_colormap(-2, 6, center=0)
pc = axs[1].pcolormesh(X, vmin=-2, vmax=6, cmap=newmap)
fig.colorbar(pc, ax=axs[1])
plt.show()

Matplotlib: How to copy a contour plot to another figure?

I have a figure with many different plots (contour plots and lots of other stuff). I want to extract the contour plot to another single figure to see more details. But I fail how to do so.
Have a look on this code:
import numpy as np
from matplotlib import gridspec as gs, pyplot as plt
# Figure 1 with many different plots.
fig1 = plt.figure()
gridSpec = gs.GridSpec(2, 3)
for i in range(6):
fig1.add_subplot(gridSpec[i])
# Create contour plot
x = np.arange(-3.0, 3.0, 0.02)
y = np.arange(-2.0, 2.0, 0.01)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) ** 4
# Plot it to a particular axes.
ax1 = fig1.axes[2]
contour = ax1.contour(X, Y, Z)
# Try to copy the contour plot to another figure (with only 1 subplot).
fig2, ax2 = plt.subplots()
# How to copy the content of ax1 to ax2?
plt.show()
This will give me the following:
I want to create a second figure with only 1 subplot and its content should be the same as you can see in top right corner of the first figure with 6 subplots.
First thing I tried was
ax2.add_collection(contour.collections[1])
but I got the error message
RuntimeError: Can not put single artist in more than one figure
This is because the content is already plottet to figure 1, so it is not possible to plot it to figure 2 as well. So I tried to make a copy of the contour plot:
from copy import deepcopy
ax2.add_collection(deepcopy(contour.collections[1]))
But this will get me a new error that copiing is not possible ...
NotImplementedError: TransformNode instances can not be copied. Consider using frozen() instead.
So .. what can I do? Any ideas for that problem? :)
Thanks a lot!
(Python 3.7.4, Matplotlib 3.1.1)

How to change the location of the symbols/text within a legend box?

I have a subplot with a single legend entry. I am placing the legend at the bottom of the figure and using mode='expand'; however, the single legend entry is placed to the very left of the legend box. To my understanding, changing kwargs such as bbox_to_anchor changes the legend box parameters but not the parameters of the symbols/text within. Below is an example to reproduce my issue.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 21)
y = np.exp(x)
z = x **2
fig, axes = plt.subplots(nrows=1, ncols=2)
axes[0].plot(x, y, color='r', label='exponential')
axes[1].plot(x, z, color='b')
# handles, labels = axes[0].get_legend_handles_labels()
plt.subplots_adjust(bottom=0.125)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code produces . How can I change the position of the symbol and text such that they are centered in the legend box?
PS: I am aware that exponential is a bad label for this subplot since it only describes the first subfigure. But, this is just for examples-sake so that I can apply it to my actual use-case.
The legend entries are placed using a HPacker object. This does not allow to be centered. The behaviour is rather that those HPackers are "justified" (similar to the "justify" option in common word processing software).
A workaround would be to create three (or any odd number of) legend entries, such that the desired entry is in the middle. This would be accomplished via the ncol argument and the use of "dummy" entries (which might be transparent and have no associated label).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 21)
y = np.exp(x)
z = x **2
fig, axes = plt.subplots(nrows=1, ncols=2)
fig.subplots_adjust(bottom=0.125)
l1, = axes[0].plot(x, y, color='r', label='exponential')
axes[1].plot(x, z, color='b')
dummy = plt.Line2D([],[], alpha=0)
fig.legend(handles=[dummy, l1, dummy],
mode='expand', loc='lower center', ncol=3)
plt.show()

How to draw Scatter plot on top of background using Basemap Python

I am trying to plot a scatter plot on a background using basemap. But it's overwriting the background. How do I retain the background?
I am using this code
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80,llcrnrlon=-180,urcrnrlon=180,lat_ts=20,resolution='c')
m.bluemarble()
x, y = m(list(longitude), list(latitude))
plt.scatter(x,y,1,marker='o',color='Red')
plt.show()
But as soon as I run the scatter plot, its overwriting background image. How can I overlay the scatter plot on the image.
This is how to plot a series of points on top of a raster map. Note that the bluemarble image is huge, so a full scale (1.0 or default) plot of it should be avoided. The code is based on yours.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# make up some data for scatter plot
lats = np.random.randint(-75, 75, size=20)
lons = np.random.randint(-179, 179, size=20)
fig = plt.gcf()
fig.set_size_inches(8, 6.5)
m = Basemap(projection='merc', \
llcrnrlat=-80, urcrnrlat=80, \
llcrnrlon=-180, urcrnrlon=180, \
lat_ts=20, \
resolution='c')
m.bluemarble(scale=0.2) # full scale will be overkill
m.drawcoastlines(color='white', linewidth=0.2) # add coastlines
x, y = m(lons, lats) # transform coordinates
plt.scatter(x, y, 10, marker='o', color='Red')
plt.show()
The resulting plot:
I realize it's an old question but in case anyone comes here with the same problem as I did.
The trick is to give a higher zorder for the scatter plot than for the .bluemarble().
m.scatter(x, y, 10, marker='o', color='Red', zorder=3)
More info here: https://matplotlib.org/3.1.0/gallery/misc/zorder_demo.html
I'm not entirely sure what you mean by "overwriting the background". When you use plt.scatter(), it will plot the points over the map, so it will display the points over the background.
Just based off the code provided, I think you're issue here is m(list(longitude), list(latitude)).
If you have multiple points in a list, you want to loop over them.
lats = [32, 38, 35]
lons = [-98, -79, -94]
x, y = m(lons, lats)
for i in range(len(lats)):
plt.scatter(x, y, marker = 'o')
If it's only one single point,
lat, lon = 32, -92
x, y = m(lon, lat)
plt.scatter(x, y, marker = 'o')
The styling of the points can be found in the matplotlib documentation.

Resources