I am trying to add an additional small colorbar for the inset axis. The current code, without that, is
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors as colors
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
A = np.linspace(1,20,20)
B = A
X,Y = np.meshgrid(A,B)
Z = X**2 + Y**2
fig, ax = plt.subplots()
im = ax.pcolor(X, Y, Z, cmap='hot_r')
ax.set_xlabel('x',fontsize=labelsize)
ax.set_ylabel('y',fontsize=labelsize)
ca = fig.colorbar(im)#, shrink=0.5, aspect=5)
axins = ax.inset_axes([0.1, 0.5, 0.25, 0.25])
axins.pcolor(A[0:4], B[0:4], Z[0:4,0:4], cmap='hot_r')
axins.tick_params(axis='both', which='major', labelsize=11)
for axis in ['top','bottom','left','right']:
axins.spines[axis].set_linewidth(1)
axins.spines[axis].set_color('gray')
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec='gray', lw=1)
plt.tight_layout()
You could create an additional inset axis for the colorbar. E.g. located just right of the inset. Then create a colorbar proving this axis (cax=...).
Please note that pcolor creates faces (large pixels) between the given x and y positions. So, you need one row and one column more of position then the number of colors. The current version of matplotlib gives a warning in case too many colors (or not enough x and y positions) are given.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
A = np.linspace(1, 20, 20)
B = A
X, Y = np.meshgrid(A, B)
Z = X ** 2 + Y ** 2
fig, ax = plt.subplots()
im = ax.pcolor(X, Y, Z[:-1, :-1], cmap='hot_r')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ca = fig.colorbar(im) # , shrink=0.5, aspect=5)
axins = ax.inset_axes([0.1, 0.5, 0.25, 0.25])
axins_cbar = ax.inset_axes([0.37, 0.5, 0.02, 0.25])
img_in = axins.pcolor(A[0:5], B[0:5], Z[0:4, 0:4], cmap='hot_r')
axins.tick_params(axis='both', which='major', labelsize=11)
for axis in ['top', 'bottom', 'left', 'right']:
axins.spines[axis].set_linewidth(1)
axins.spines[axis].set_color('gray')
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec='gray', lw=1)
fig.colorbar(img_in, cax=axins_cbar)
plt.tight_layout()
plt.show()
Related
How to add axis label (x and y) and rotate y axis numbers with Matplotlib like on the image below ?
I tried plt.yticks(rotation=45) to rotate the y axis numbers but it's not taken into account.
Besides, I'm also trying to have one 0 instead of two in my example code and a square grid instead of rectangles.
from mpl_toolkits.axisartist.axislines import SubplotZero
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = SubplotZero(fig, 111)
fig.add_subplot(ax)
for direction in ["xzero", "yzero"]:
# adds arrows at the ends of each axis
ax.axis[direction].set_axisline_style('->')
# adds X and Y-axis from the origin
ax.axis[direction].set_visible(True)
ax.axis['yzero'].set_ticklabel_direction("-")
for direction in ["left", "right", "bottom", "top"]:
# hides borders
ax.axis[direction].set_visible(False)
x = np.linspace(-5, 5, 100)
ax.plot(x, -x**2+16, color="#ab74a6", linewidth=3)
plt.title(r'$y = -x^2+16$')
plt.yticks(rotation=45)
plt.axis([-5, 5, -10, 20])
plt.grid(True)
plt.show()
Here's a working code example using spines rather than SubplotZero:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(111)
x = np.linspace(-5, 5, 100)
ax.plot(x, -x**2+16, color="#ab74a6", linewidth=3)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# hide one of the zero labels and adjust the other
ax.yaxis.get_major_ticks()[3].label1.set_visible(False)
ax.xaxis.get_major_ticks()[3].label1.set_horizontalalignment("right")
ax.plot(1, 0, ">k", transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", transform=ax.get_xaxis_transform(), clip_on=False)
ax.axis('equal')
ax.set_xlabel('x', position=(1,0), ha='right')
ax.set_ylabel('y', position=(0,1), ha='right', rotation=0)
plt.title(r'$y = -x^2+16$', y=1.08)
plt.grid(True)
plt.show()
How can I reduce the distance between the numbering of an axis and the ticks corresponding to them. I tried using pad=0 for the tick_params but it doesn't seem to work. Below is a reproducible (simplified) code of my issue (and the figure):
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
plt.rcParams["figure.figsize"] = (10,10)
fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlabel("X" , fontsize=20)
ax.set_ylabel("Y", fontsize=20)
ax.set_zlabel("Z" , fontsize=20)
ax.view_init(azim=-20)
ax.tick_params(axis='x', which='major', pad=0)
x = np.arange(0,10,0.01)
y = np.ones(len(x))
z = np.sin(x)
plt.plot(x,y,z)
Changing the values of pad seem to not have any effect. Note: I need the plot in that specific orientation (azim=-20). How can I achieve what I need? Thank you!
The pad argument also takes negative values to bring the ticklabels closer to the ticks.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
fig = plt.figure()
ax = axes3d.Axes3D(fig)
ax.set_xlabel("X" , fontsize=20)
ax.set_ylabel("Y", fontsize=20)
ax.set_zlabel("Z" , fontsize=20)
ax.view_init(azim=-20)
ax.tick_params(axis='x', which='major', pad=-5)
x = np.arange(0, 10, 0.01)
y = np.ones(len(x))
z = np.sin(x)
plt.plot(x, y, z)
plt.show()
EDIT: Alternative outcome with set figure size and dpi value.
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
mpl.rcParams["figure.figsize"] = 10, 10
mpl.rcParams["figure.dpi"] = 100
fig = plt.figure()
ax = axes3d.Axes3D(fig)
ax.set_xlabel("X" , fontsize=20)
ax.set_ylabel("Y", fontsize=20)
ax.set_zlabel("Z" , fontsize=20)
ax.view_init(azim=-20)
ax.tick_params(axis='x', which='major', pad=-5)
x = np.arange(0, 10, 0.01)
y = np.ones(len(x))
z = np.sin(x)
plt.plot(x, y, z)
plt.show()
I am building a figure with a primary axis that is a scatter plot and a zoomed axis which focuses on a particular region of the primary axis, both of which have gridlines. When I place the zoomed axis as an inset, it "covers" up some of the primary axis data. I want to be able to show the primary axis data (zorder=100) through the zoomed axis, so I set the zoomed axis to be transparent (alpha=0). Finally, I want the primary axis gridlines to "cut-off" when they meet the zoomed axis (zorder=10) but I want to show the zoomed axis gridlines (zorder=50). Is this possible? Below is my attempt:
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
import numpy as np
fig = plt.figure(figsize=(10,7.5))
gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[20,1], height_ratios=[1])
ax = plt.subplot(111)
## data
xx = np.linspace(1,100,num=100) + 20 * np.random.normal(0,1,100)
yy = np.linspace(1,100,num=100) + 10 * np.random.normal(0,1,100)
## scatter
sc = ax.scatter(xx, yy, s=250, alpha=0.35, zorder=100)
ax.plot(np.linspace(-100,200,301), np.linspace(-100,200,301),)
ax.set_xlim((0, 100))
ax.set_ylim((0, 100))
ax.grid(linestyle="--", zorder=10)
## zoom
axins = zoomed_inset_axes(ax, 2, loc="upper left")
scins = axins.scatter(xx, yy, s=100, alpha=0.35, zorder=50, marker=".", c="red")
axins.plot(np.linspace(-100,200,301), np.linspace(-100,200,301), c="red")
axins.set_xlim((70, 90))
axins.set_ylim((70, 90))
mark_inset(ax, axins, loc1=1, loc2=4, fc="none", ec="0.5")
axins.grid(linestyle="--", zorder=50)
plt.show()
In particular, one of the blue data points near x=80 gets cut off. I can set axins.patch.set_alpha(0.0), but then it doesn't remove the primary grid lines.
One option is indeed to put a white patch (white rectangle) in ax at the position where axins lives and set that patches' zorder to higher than the one from the gridlines, but lower than the one from the scatter.
# Set axins' background patch invisible
axins.patch.set_visible(False)
# Create a new patch at the position of the axins axes.
rect = matplotlib.patches.Rectangle((0,0), 1,1,
fill=True, facecolor="white", edgecolor="red",zorder=25,
transform=axins.transAxes)
ax.add_patch(rect)
Thanks to #ImportanceOfBeingErnest for the suggestion. It works to add a rectangle with an intermediate zorder to ax per the following (where I've left the red outline of the rectangle):
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
import matplotlib.patches as patches
import numpy as np
## data
xx = np.linspace(1,100,num=100) + 20 * np.random.normal(0,1,100)
yy = np.linspace(1,100,num=100) + 10 * np.random.normal(0,1,100)
## fig
fig = plt.figure(figsize=(10,7.5))
gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[20,1], height_ratios=[1])
ax = plt.subplot(111)
## scatter
sc = ax.scatter(xx, yy, s=250, alpha=0.35, zorder=100)
ax.plot(np.linspace(-100,200,301), np.linspace(-100,200,301))
ax.set_xlim((0, 100))
ax.set_ylim((0, 100))
ax.grid(linestyle="--", zorder=10)
ax.patches.extend([patches.Rectangle((0.2, 0.6), 0.4, 0.4,
fill=True, facecolor="white", edgecolor="red",zorder=25,
transform=ax.transAxes, figure=ax)])
## zoom
axins = zoomed_inset_axes(ax, 2,
bbox_to_anchor=(0.6, 1.0, 0.0, 0.0),
bbox_transform=ax.transAxes)
scins = axins.scatter(xx, yy, s=100, alpha=0.35, zorder=50, marker=".", c="red")
axins.plot(np.linspace(-100,200,301), np.linspace(-100,200,301), c="red")
axins.set_xlim((70, 90))
axins.set_ylim((70, 90))
axins.patch.set_alpha(0.0)
mark_inset(ax, axins, loc1=1, loc2=4, fc="none", ec="0.5")
axins.grid(linestyle="--", zorder=50)
plt.show()
This is a very similar question to "How to plot pcolor colorbar in a different subplot - matplotlib". I am trying to plot a filled contour plot and a line plot with a shared axis and the colorbar in a separate subplot (i.e. so it doesn't take up space for the contourf axis and thus muck up the x-axis sharing). However, the x-axis in my code does not rescale nicely:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 2, height_ratios=[1, 2], width_ratios=[2, 1])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
ax3 = fig.add_subplot(gs[1, 1])
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.plot(x, y2, color='g')
plt.tick_params(which='both', top=False, right=False)
cbar = plt.colorbar(cont, cax=ax3)
cbar.set_label('Intensity', rotation=270, labelpad=20)
plt.tight_layout()
plt.show()
which produces an x-axis scaled from 0 to 20 (inclusive) rather than 0 to 19, which means there is unsightly whitespace in the filled contour plot. Commenting out the sharex=ax1 in the above code means that the x-axis for the contour plot is scaled nicely, but not for the line plot above it and the plt.tick_params code has no effect on either axis.
Is there a way of solving this?
You could also turn off the autoscaling of x-axis for all subsequent call of plot on this axis so that it keeps the range set by contourf and sharex=True :
ax2.set_autoscalex_on(False)
This comes even before your call to ax2.plot() and I think it is better than calling ax2.set_xlim(0, 19) since you do not need to know what are the actual limit of your x-axis that may be needed.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 1, height_ratios=[1, 2], width_ratios=[2])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.set_autoscalex_on(False)
ax2.plot(x, y2, color='g')
axins = inset_axes(ax1,
width="5%", # width = 10% of parent_bbox width
height="100%", # height : 50%
loc=6,
bbox_to_anchor=(1.05, 0., 1, 1),
bbox_transform=ax1.transAxes,
borderpad=0,
)
cbar = plt.colorbar(cont, cax=axins)
plt.show()
You can use inset_axes for this without added another axis.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 2, height_ratios=[1, 2], width_ratios=[2, 1])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.plot(x, y2, color='g')
plt.tick_params(which='both', top=False, right=False)
axins = inset_axes(ax1,
width="5%", # width = 10% of parent_bbox width
height="100%", # height : 50%
loc=6,
bbox_to_anchor=(1.05, 0., 1, 1),
bbox_transform=ax1.transAxes,
borderpad=0,
)
cbar = plt.colorbar(cont, cax=axins)
plt.savefig('figure.jpg',bbox_inches='tight',dpi=200)
I'm trying to generate two subplots side by side, sharing the y axis, with a single colorbar for both.
This is a MWE of my code:
import matplotlib.pyplot as plt
import numpy as np
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
ax0 = plt.subplot(121)
plt.scatter(x1, y, c=z, cmap=cm)
ax1 = plt.subplot(122)
# make these y tick labels invisible
plt.setp(ax1.get_yticklabels(), visible=False)
plt.scatter(x2, y, c=z, cmap=cm)
cbar = plt.colorbar()
plt.show()
what this returns is a left subplot slightly larger horizontally than the right one since this last includes the colorbar, see below:
I've tried using ax.set_aspect('equal') but since the x axis are not in the same range the result looks awful.
I need both these plots to be displayed squared. How can I do this?
To expend my comment that one can make 3 plots, plot the colorbar() in the 3rd one, the data plots in the 1st and 2nd. This way, if necessary, we are free to do anything we want to the 1st and 2nd plots:
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure(figsize=(12,6))
gs=gridspec.GridSpec(1,3, width_ratios=[4,4,0.2])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax3 = plt.subplot(gs[2])
cm = plt.cm.get_cmap('RdYlBu')
ax1.scatter(x1, y, c=z, cmap=cm)
SC=ax2.scatter(x2, y, c=z, cmap=cm)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.colorbar(SC, cax=ax3)
plt.tight_layout()
plt.savefig('temp.png')
Updated - here is another option without using GridSpec.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x_vals = np.random.rand(N)
y_vals = np.random.rand(N)
z1_vals = np.random.rand(N)
z2_vals = np.random.rand(N)
minimum_z = min(np.min(z1_vals), np.min(z2_vals))
maximum_z = max(np.max(z1_vals), np.max(z2_vals))
fig, axis_array = plt.subplots(1,2, figsize = (20, 10), subplot_kw = {'aspect':1})
ax0 = axis_array[0].scatter(x_vals, y_vals, c = z1_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
ax1 = axis_array[1].scatter(x_vals, y_vals, c = z2_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
cax = fig.add_axes([0.95, 0.05, 0.02, 0.95]) #this locates the axis that is used for your colorbar. It is scaled 0 - 1.
fig.colorbar(ax0, cax, orientation = 'vertical') #'ax0' tells it which plot to base the colors on
plt.show()