changing size of a plot in a subplot figure - python

i create a figure with 4 subplots (2 x 2), where 3 of them are of the type imshow and the other is errorbar. Each imshow plots have in addition a colorbar at the right side of them. I would like to resize my 3rd plot, that the area of the graph would be exactly under the one above it (with out colorbar)
as example (this is what i now have):
How could i resize the 3rd plot?
Regards

To adjust the dimensions of an axes instance, you need to use the set_position() method. This applies to subplotAxes as well. To get the current position/dimensions of the axis, use the get_position() method, which returns a Bbox instance. For me, it's conceptually easier to just interact with the position, ie [left,bottom,right,top] limits. To access this information from a Bbox, the bounds property.
Here I apply these methods to something similar to your example above:
import matplotlib.pyplot as plt
import numpy as np
x,y = np.random.rand(2,10)
img = np.random.rand(10,10)
fig = plt.figure()
ax1 = fig.add_subplot(221)
im = ax1.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
ax2 = fig.add_subplot(222)
im = ax2.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
ax3 = fig.add_subplot(223)
ax3.plot(x,y)
ax3.axis([0,1,0,1])
ax4 = fig.add_subplot(224)
im = ax4.imshow(img,extent=[0,1,0,1])
plt.colorbar(im)
pos4 = ax4.get_position().bounds
pos1 = ax1.get_position().bounds
# set the x limits (left and right) to first axes limits
# set the y limits (bottom and top) to the last axes limits
newpos = [pos1[0],pos4[1],pos1[2],pos4[3]]
ax3.set_position(newpos)
plt.show()
You may feel that the two plots do not exactly look the same (in my rendering, the left or xmin position is not quite right), so feel free to adjust the position until you get the desired effect.

Related

matplotlib change colorbar height within own axes

I'm currently trying to create a stackplot of graphs, of which my first two have colorbars. To do this nicely, I'm using GridSpec to define two columns, with the second being much thinner and specifically for colorbars (or other out-of-plot things like legends).
grids = gs.GridSpec(5, 2, width_ratios=[1, 0.01])
ax1 = fig.add_subplot(grids[0, 0])
cax1 = fig.add_subplot(grids[0, 1])
The problem is that for these top two plots, the ticklabels of my colorbar overlap slightly, due to the fact that I've got zero horizontal space between my plots.
I know that there are ways to control the height of the colorbar, but they seem to rely on the colorbar making it's own axes by borrowing space from the parent axes. I was wondering if there was any way to control how much space (or specifically, height) the colorbar takes up when you use the cax kwarg
fig.colorbar(im1, cax=cax1, extend='max')
or if it defaults (immutably) to take up the entire height of the axes given to it.
Thanks!
EDIT: Here's an image of the issue I'm struggling with.
If I could make the second slightly shorter, or shift the upper one slightly up then it wouldn't be an issue. Unfortunately since I've used GridSpec (which has been amazing otherwise) I'm constrained to the limits of the axes.
I don't think there is any way to ask colorbar to not fill the whole cax. However, it is fairly trivial to shrink the size of the cax before (or after actually) plotting the colorbar.
I wrote this small function:
def shrink_cbar(ax, shrink=0.9):
b = ax.get_position()
new_h = b.height*shrink
pad = (b.height-new_h)/2.
new_y0 = b.y0 + pad
new_y1 = b.y1 - pad
b.y0 = new_y0
b.y1 = new_y1
ax.set_position(b)
which can be used like so:
fig = plt.figure()
grids = gs.GridSpec(2, 2, width_ratios=[1, 0.01])
ax1 = fig.add_subplot(grids[0, 0])
cax1 = fig.add_subplot(grids[0, 1])
ax2 = fig.add_subplot(grids[1, 0])
cax2 = fig.add_subplot(grids[1, 1])
shrink_cbar(cax2, 0.75)

Using color scales as axes in matplotlib

I'm trying to create a visualization that varies color (specifically the H and V values of an HSV color scheme while keeping S constant), while representing the response of a given function to those colors.
Effectively, it's a heat map where the x and y axes are colors rather than numbers. Hunting through the matplotlib gallery I can find a lot of examples based on colorbars such as those found here, and here.
The colorbar implementation is close to what I'm looking for, with these important caveats:
I'm looking to align the colors as ticks on the main figure, rather than adding ticks to the colorbar itself. Principally this calls for making sure the plot and the colorbar are aligned, and I haven't found any way of actually guaranteeing this.
I'm trying to ensure that the color bar will display on the left of the figure (in place of the standard x-axis) rather than to the right.
The second point sounds trivial, but I haven't found any documented way of achieving it unfortunately.
Is there any way of creating a plot like this in matplotlib that would be considerably less effort than creating it from scratch in d3 or a similar lower-level visualization library?
I'm still not quite sure about it; but I'll give a try. Sorry if I misunderstood it.
Major thoughts are using GridSpec to solve your two requirements: aligning the "color axes" and put them beside the classic axes. The alignment should be correct because corresponding axes between ax_x/ax_y and the main ax are the same.
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
from matplotlib.gridspec import GridSpec
import numpy as np
# Create a spectrum sample
# Convert HSV to RGB so that matplotlib can plot;
# hsv_to_rgb assumes values to be in range [0, 1]
N = 0.001
v_y, h_x = np.mgrid[0:1:N, 0:1:N]
c = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), v_y], axis=2))
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
c_y = hsv_to_rgb(np.stack([np.zeros(h_x.shape), np.ones(h_x.shape), v_y], axis=2))
fig = plt.figure()
# Ratio to adjust width for "x axis" and "y axis"
fig_ratio = np.divide(*fig.get_size_inches())
gs = GridSpec(2, 2, wspace=0.0, hspace=0.0,
width_ratios=[1, 20], height_ratios=[20/fig_ratio, 1])
# Lower-left corner is ignored
ax_y = plt.subplot(gs[0])
ax = plt.subplot(gs[1])
ax_x = plt.subplot(gs[3])
# Image are stretched to fit the ax since numbers are hided or not important in this figure.
img = ax.imshow(c, aspect='auto', origin='lower')
# Colorbar on img won't give correct results since it is plot with raw RGB values
img_x = ax_x.imshow(c_x, aspect='auto', origin='lower')
img_y = ax_y.imshow(c_y, aspect='auto', origin='lower')
# Remove ticks and ticklabels
for ax in [ax_y, ax, ax_x]:
ax.tick_params(left=False, bottom=False,
labelleft=False, labelbottom=False)
plt.show()
Response to the comment:
To clarify, you're making three plots, and using imshow plots as axes by assigning them to quadrants of the grid?
Yes, it's a 2x2 grid and I ignored the lower-left one. The documentation might not be great but what I did is similar to this part.
And presumably if I wanted to add space between the axes here and the main plot I would increase wspace and hspace?
Yes, it is briefly demonstrated in this part of documentation. Besides, I adjusted it with width_ratios and height_ratios so that 3 parts of the figure are not the same size, like this.
Also, just to confirm, there is a fully black axis on the bottom of this image, and it's not a misalignment of the left axis.
The bottom is the colored x axis. It is black because I thought it corresponds to v=0. If you change
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.zeros(v_y.shape)], axis=2))
to
c_x = hsv_to_rgb(np.stack([h_x, np.ones(h_x.shape), np.ones(v_y.shape)], axis=2))
You would get this figure, proving it's not misaligned:
If it's easier, you can also ignore the whole hsv thing, use a gray box or something as the central image.
I'm sorry but I'm really slow on this. I'm still having no idea what you want to show in the figure. So I don't know how to help. If you remove or comment out the line
img = ax.imshow(c, aspect='auto', origin='lower')
You got this:

Matplotlib: Sharing axes when having 3 graphs 2 at the left and 1 at the right

I have following graph:
However, I want that graphs 221 and 223 share the same x axis. I have the following code:
self.fig_part_1 = plt.figure()
self.plots_part_1 = [
plt.subplot(221),
plt.subplot(223),
plt.subplot(122),
]
How can I achieve that? In the end I do not want the numbers of axis x in plot 221 to be shown.
(This is mostly a comment to #H. Rev. but I post it as an "answer" to get nicer code formatting)
I think it is way better to just add the subplots manually, since as you implemented it now it will give two axes that you just throw away. They might even give problems with overlapping axis-ticks and a lot of confusion in general. I believe it is better to create the figure first, and then add axes one by one. This way also solves the problem by having to "update" the current figure with plt.figure(self.f.number) since you have direct access to e.g. fig_N
import matplotlib.pyplot as plt
fig1 = plt.figure()
# fig2 = plt.figure() # more figures are easily accessible
# fig3 = plt.figure() # more figures are easily accessible
ax11 = fig1.add_subplot(221) # add subplot into first position in a 2x2 grid (upper left)
ax12 = fig1.add_subplot(223, sharex=ax11) # add to third position in 2x2 grid (lower left) and sharex with ax11
ax13 = fig1.add_subplot(122) # add subplot to cover both upper and lower right, in a 2x2 grid. This is the same as the rightmost panel in a 1x2 grid.
# ax21 = fig2.add_subplot(211) # add axes to the extra figures
# ax21 = fig2.add_subplot(212) # add axes to the extra figures
# ax31 = fig3.add_subplot(111) # add axes to the extra figures
plt.show()
Just use plt.subplots (different from plt.subplot) to define all your axes, with the option sharex=True:
f, axes = plt.subplots(2,2, sharex=True)
plt.subplot(122)
plt.show()
Note that the second call with larger subplot array overlay the preceding one.
Example (could not display image due to reputation...)

Matplotlib - axvspan vs subplots

I'm writing a pythonic script for a coastal engineering application which should output, amongst other things, a figure with two subplots.
The problem is that I would like to shade a section of both subplots using plt.axvspan() but for some reason it only shades one of them.
Please find below an excerpt of the section of the code where I set up the plots as well as the figure that it's currently outputting (link after code).
Thanks for your help, and sorry if this is a rookie question (but it just happens that I am indeed a rookie in Python... and programming in general) but I couldn't find an answer for this anywhere else.
Feel free to add any comments to the code.
# PLOTTING
# now we generate a figure with the bathymetry vs required m50 and another figure with bathy vs Hs
#1. Generate plots
fig = plt.figure() # Generate Figure
ax = fig.add_subplot(211) # add the first plot to the figure.
depth = ax.plot(results[:,0],results[:,1]*-1,label="Depth [mDMD]") #plot the first set of data onto the first set of axis.
ax2 = ax.twinx() # generate a secondary vertical axis with the same horizontal axis as the first
m50 = ax2.plot(results[:,0],results[:,6],"r",label="M50 [kg]") # plot the second set of data onto the second vertical axis
ax3 = fig.add_subplot(212) # generate the second subplot
hs = ax3.plot(results[:,0],results[:,2],"g",label="Hs(m)")
#Now we want to find where breaking starts to occur so we shade it on the plot.
xBreakingDistance = results[numpy.argmax(breakingIndex),0]
# and now we plot a box from the origin to the depth of breaking.
plt.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) # this box is called a span in matplotlib (also works for axhspan)
# and then we write BREAKING ZONE in the box we just created
yLimits = ax.get_ylim() # first we get the range of y being plotted
yMiddle = (float(yLimits[1])-float(yLimits[0])) / 2 + yLimits[0] # then we calculate the middle value in y (to center the text)
xMiddle = xBreakingDistance / 2 # and then the middle value in x (to center the text)
#now we write BREAKING ZONE in the center of the box.
ax.text(xMiddle,yMiddle,"BREAKING ZONE",fontweight="bold",rotation=90,verticalalignment="center",horizontalalignment="center")
#FIGURE FORMATTING
ax.set_xlabel("Distance [m]") # define x label
ax.set_ylabel("Depth [mDMD]") # define y label on the first vertical axis (ax)
ax2.set_ylabel("M50 [kg]") # define y label on the second vertical axis (ax2)
ax.grid() # show grid
ax3.set_xlabel("Distance[m]") #define x label
ax3.set_ylabel("Hs[m]") # define y label
ax3.grid()
plt.tight_layout() # minimize subplot labels overlapping
# generating a label on a plot with 2 vertical axis is not very intuitive. Normally we would just write ax.label(loc=0)
combined_plots = depth+m50 #first we need to combine the plots in a vector
combined_labels = [i.get_label() for i in combined_plots] # and then we combine the labels
ax.legend(combined_plots,combined_labels,loc=0) # and finally we plot the combined_labels of the combined_plots
plt.savefig("Required M50(kg) along the trench.png",dpi=1000)
plt.close(fig)
Output Figure:
By just calling plt.axvspan, you are telling matplotlib to create the axvspan on the currently active axes (i.e. in this case, the last one you created, ax3)
You need to plot the axvspan on both of the axes you would like for it to appear on. In this case, ax and ax3.
So, you could do:
ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
ax3.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
or in one line:
[this_ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) for this_ax in [ax,ax3]]
It's difficult to analyze your code and not being able to reproduce it. I advise you to build a minimal example. In any case notice that you are calling "plt.axvspan(" which is general call to the library.
You need to specifically state that you want this in both "ax" and "ax2" (i think).
Also if you need more control consider using Patches (I don't know axvspan):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
fig1.savefig('rect1.png', dpi=90, bbox_inches='tight')
See that call to "ax1" in the example? Just make something similar to yours. Or just add axvspan to each of your plots.

Matplotlib: Adjust legend location/position

I'm creating a figure with multiple subplots. One of these subplots is giving me some trouble, as none of the axes corners or centers are free (or can be freed up) for placing the legend. What I'd like to do is to have the legend placed somewhere in between the 'upper left' and 'center left' locations, while keeping the padding between it and the y-axis equal to the legends in the other subplots (that are placed using one of the predefined legend location keywords).
I know I can specify a custom position by using loc=(x,y), but then I can't figure out how to get the padding between the legend and the y-axis to be equal to that used by the other legends. Would it be possible to somehow use the borderaxespad property of the first legend? Though I'm not succeeding at getting that to work.
Any suggestions would be most welcome!
Edit: Here is a (very simplified) illustration of the problem:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, sharex=False, sharey=False)
ax[0].axhline(y=1, label='one')
ax[0].axhline(y=2, label='two')
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
ax[1].axhline(y=1, label='one')
ax[1].axhline(y=2, label='two')
ax[1].axhline(y=3, label='three')
ax[1].set_ylim([0.8,3.2])
ax[1].legend(loc=2)
plt.show()
What I'd like is that the legend in the right plot is moved down somewhat so it no longer overlaps with the line.
As a last resort I could change the axis limits, but I would very much like to avoid that.
I saw the answer you posted and tried it out. The problem however is that it is also depended on the figure size.
Here's a new try:
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(0, 10, 10000)
y = numpy.cos(x) + 2.
x_value = .014 #Offset by eye
y_value = .55
fig, ax = plt.subplots(1, 2, sharex = False, sharey = False)
fig.set_size_inches(50,30)
ax[0].plot(x, y, label = "cos")
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
line1 ,= ax[1].plot(x,y)
ax[1].set_ylim([0.8,3.2])
axbox = ax[1].get_position()
fig.legend([line1], ["cos"], loc = (axbox.x0 + x_value, axbox.y0 + y_value))
plt.show()
So what I am now doing is basically getting the coordinates from the subplot. I then create the legend based on the dimensions of the entire figure. Hence, the figure size does not change anything to the legend positioning anymore.
With the values for x_value and y_value the legend can be positioned in the subplot. x_value has been eyeballed for a good correspondence with the "normal" legend. This value can be changed at your desire. y_value determines the height of the legend.
Good luck!
After spending way too much time on this, I've come up with the following satisfactory solution (the Transformations Tutorial definitely helped):
bapad = plt.rcParams['legend.borderaxespad']
fontsize = plt.rcParams['font.size']
axline = plt.rcParams['axes.linewidth'] #need this, otherwise the result will be off by a few pixels
pad_points = bapad*fontsize + axline #padding is defined in relative to font size
pad_inches = pad_points/72.0 #convert from points to inches
pad_pixels = pad_inches*fig.dpi #convert from inches to pixels using the figure's dpi
Then, I found that both of the following work and give the same value for the padding:
# Define inverse transform, transforms display coordinates (pixels) to axes coordinates
inv = ax[1].transAxes.inverted()
# Inverse transform two points on the display and find the relative distance
pad_axes = inv.transform((pad_pixels, 0)) - inv.transform((0,0))
pad_xaxis = pad_axes[0]
or
# Find how may pixels there are on the x-axis
x_pixels = ax[1].transAxes.transform((1,0)) - ax[1].transAxes.transform((0,0))
# Compute the ratio between the pixel offset and the total amount of pixels
pad_xaxis = pad_pixels/x_pixels[0]
And then set the legend with:
ax[1].legend(loc=(pad_xaxis,0.6))
Plot:

Categories

Resources