How do I make subplots from normalized figure coordinates only? - python

I have normalized figure coordinates(left, bottom, right, top) of subplots in a figure. These coordinates are generated by a different software. The number of subplots is not fixed. I'm trying to generate a figure using these coordinates.
Here is a small example of what I've tried.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
rects=[[0, 0.5, 0.75, 1],[0.25, 0, 1, 0.5]]
for i in range(2):
gs = gridspec.GridSpec(1, 1)
ax = fig.add_subplot(gs[0])
ax.plot([1, 2, i])
ax.set_xlabel(i)
gs.tight_layout(fig, rect=rects[i])
plt.show()
This generates the figure correctly as per my requirement.
But when I tried to implement the same in The object-oriented interface the output is different.
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
fig = Figure()
canvas=FigureCanvas(fig)
rects=[[0, 0.5, 0.75, 1],[0.25, 0, 1, 0.5]]
for i in range(2):
gs = gridspec.GridSpec(1, 1)
ax = fig.add_subplot(gs[0])
ax.plot([1, 2, i])
ax.set_xlabel(i)
gs.tight_layout(fig, rect=rects[i])
fig.savefig('test')
Is my approach correct? Why output of pyplot is different from Object Oriented Interface? How do I get it working in Object Oriented Interface?
Thanks

It is a little bit by coincidence that the first code works at all. I wouldn't rely on that.
Now it seems you want to position the axes by coordinates. In that case, one would rather use add_axes, and calculate the extends from the rectangles,
import matplotlib.pyplot as plt
fig = plt.figure()
rects=[[0, 0.5, 0.75, 1],[0.25, 0, 1, 0.5]]
for i, rect in enumerate(rects):
a,b,c,d = rect
ext = [a,b,c-a,d-b]
ax = fig.add_axes(ext)
ax.plot([1, 2, i])
ax.set_xlabel(i)
plt.show()
The resulting figure may not make too much sense, which is due to the coordinates in use. Maybe, those are not the ones you meant to be using?
A similar figure to the one in the question may also be produced with a single gridspec, like
import matplotlib.pyplot as plt
fig = plt.figure()
gs = fig.add_gridspec(2,3)
ax1 = fig.add_subplot(gs[0,:2])
ax2 = fig.add_subplot(gs[1,1:])
for i, ax in enumerate([ax1, ax2]):
ax.plot([1, 2, i])
ax.set_xlabel(i)
fig.tight_layout()
plt.show()

Related

Centering Custom y-ticks Imshow

I'm trying to center yaxis tick marks on an imshow image similar to the one here. In the image, each row is a separate "profile" that I've stacked together. I want the tick location to be at the center of each horizontal section, like this (made in Powerpoint).
Here's some working code to make the images above:
import numpy as np
import matplotlib.pyplot as plt
td = [0,1,2,5,10,15,25,66]
N = len(td)
profiles = np.random.randn(N, 501).cumsum(axis=1)
fig, ax = plt.subplots(1,1)
ax.imshow(profiles, interpolation='none', aspect='auto', extent=[0, 500, N-1, 0])
ax.set_yticks(range(N))
plt.show()
Is there an easy way to do this? Let me know how I can clarify my question. If possible, I'd like to learn how to do this with matplotlib.axes (i.e., fig, ax = plt.subplots(1,1)...). Thanks!
You can manually set y ticks and tick labels at 0.5, 1.5 etc. (matplotlib 3.5.0 or above to do this in one call to set_ylabel):
import numpy as np
import matplotlib.pyplot as plt
td = [0,1,2,5,10,15,25,66]
N = len(td)
profiles = np.random.randn(N, 501).cumsum(axis=1)
fig, ax = plt.subplots()
ax.imshow(profiles, interpolation='none', aspect='auto', extent=[0, 500, N, 0])
ax.set_yticks(np.arange(N) + 0.5, [f'{y}' for y in td])

How to reduce horizontal spacing between subplots in matplotlib python? [duplicate]

The code below produces gaps between the subplots. How do I remove the gaps between the subplots and make the image a tight grid?
import matplotlib.pyplot as plt
for i in range(16):
i = i + 1
ax1 = plt.subplot(4, 4, i)
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.subplots_adjust(wspace=None, hspace=None)
plt.show()
The problem is the use of aspect='equal', which prevents the subplots from stretching to an arbitrary aspect ratio and filling up all the empty space.
Normally, this would work:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(wspace=0, hspace=0)
The result is this:
However, with aspect='equal', as in the following code:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
plt.subplots_adjust(wspace=0, hspace=0)
This is what we get:
The difference in this second case is that you've forced the x- and y-axes to have the same number of units/pixel. Since the axes go from 0 to 1 by default (i.e., before you plot anything), using aspect='equal' forces each axis to be a square. Since the figure is not a square, pyplot adds in extra spacing between the axes horizontally.
To get around this problem, you can set your figure to have the correct aspect ratio. We're going to use the object-oriented pyplot interface here, which I consider to be superior in general:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8)) # Notice the equal aspect ratio
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
fig.subplots_adjust(wspace=0, hspace=0)
Here's the result:
You can use gridspec to control the spacing between axes. There's more information here.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.figure(figsize = (4,4))
gs1 = gridspec.GridSpec(4, 4)
gs1.update(wspace=0.025, hspace=0.05) # set the spacing between axes.
for i in range(16):
# i = i + 1 # grid spec indexes from 0
ax1 = plt.subplot(gs1[i])
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.show()
Without resorting gridspec entirely, the following might also be used to remove the gaps by setting wspace and hspace to zero:
import matplotlib.pyplot as plt
plt.clf()
f, axarr = plt.subplots(4, 4, gridspec_kw = {'wspace':0, 'hspace':0})
for i, ax in enumerate(f.axes):
ax.grid('on', linestyle='--')
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.show()
plt.close()
Resulting in:
With recent matplotlib versions you might want to try Constrained Layout. This does (or at least did) not work with plt.subplot() however, so you need to use plt.subplots() instead:
fig, axs = plt.subplots(4, 4, constrained_layout=True)
Have you tried plt.tight_layout()?
with plt.tight_layout()
without it:
Or: something like this (use add_axes)
left=[0.1,0.3,0.5,0.7]
width=[0.2,0.2, 0.2, 0.2]
rectLS=[]
for x in left:
for y in left:
rectLS.append([x, y, 0.2, 0.2])
axLS=[]
fig=plt.figure()
axLS.append(fig.add_axes(rectLS[0]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[4]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[8]))
for i in [5,6,7]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[12]))
for i in [9,10,11]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
If you don't need to share axes, then simply axLS=map(fig.add_axes, rectLS)
Another method is to use the pad keyword from plt.subplots_adjust(), which also accepts negative values:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(pad=-5.0)
Additionally, to remove the white at the outer fringe of all subplots (i.e. the canvas), always save with plt.savefig(fname, bbox_inches="tight").

Colorbar is attached to an existing axis, instead of filling into it

The problem is a bit hard to describe, but very easy to show. I create a grid with subplots on it, where the right column is filled by a tall subplot (approximately following this) which I want to use for the colourbar. Creating a new axis of a given size and using it for a colourbar is done in many code samples (see for example here), but it's not working for me.
Here's an example with a plot layout the same as my real plot that reproduces the problem:
import matplotlib.pyplot as plt
import matplotlib.colors as clt
import numpy as np
fig, axes = plt.subplots(3, 2, figsize=(15,8), tight_layout=True,
gridspec_kw={'width_ratios': [1, 0.02],
'height_ratios': [2, 1, 1]})
x, y = np.random.rand(500000), np.random.rand(500000)
counts, xedges, yedges, im = axes[0, 0].hist2d(x, y, bins=(149, 336), norm=clt.LogNorm(), cmap='inferno_r')
axes[1, 0].plot(np.random.rand(2184))
axes[2, 0].plot(np.random.rand(2184))
gs = axes[0, 1].get_gridspec()
for ax in axes[:, 1]:
ax.remove()
axbig = fig.add_subplot(gs[0:, -1])
bar = fig.colorbar(im, ax=axbig)
axes[0, 0].set_ylabel("2D histogram")
axes[1, 0].set_ylabel("unrelated data")
axes[2, 0].set_ylabel("other unrelated")
bar.set_label("colourbar")
(note that I use add_subplot(gs[0:, -1]) to make the tall subplot, but something like add_axes([0.8, 0.1, 0.03, 0.8]) has the same effect)
And the output:
Notice how the colourbar is added as a tiny little new axis, onto the existing axis which I created for it. I would expect it to fill in the existing axis, as in this or this example. What's going wrong? I'm running matplotlib 3.3.1 from inside spyder 5.0.0 with python 3.8.
Your original problem, that you didn't want one of three axes squished is explicitly taken care of with constrained_layout. https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html#suptitle
I think people are for some reason scared off by the warning on the CL guide, but that is really for folks running production code that must be pixel identical each run. For most users CL is a better option than tight_layout.
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
fig, axs = plt.subplots(3, 1, figsize=(7,4), constrained_layout=True,
gridspec_kw={'height_ratios': [2, 1, 1]})
x, y = np.random.rand(500000), np.random.rand(500000)
res = axs[0].hist2d(x, y, bins=(149, 336),
norm=mcolors.LogNorm(), cmap='inferno_r')
axs[1].plot(np.random.rand(2184))
axs[2].plot(np.random.rand(2184))
fig.colorbar(res[3], ax=axs[0])
plt.show()

How to enable the whole frame of axis when doing 3-d plot in matplotlib

I have been trying to add additional axis ticks to the top of the 3-d axis, such that a 'cube' of axis is showed, instead of only 3 sides as default does, but without success.
Does anyone have an idea of how to do that?
And another minor thing is that how to set the color of the grid?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d',frame_on=True) # first try, nothing happened
ax.plot([1,], [1,], [1,], marker='o')
ax_grid_color = (0.9, 0.1, 0.1, 1.0)
ax.grid(color=ax_grid_color) # doesn't work
ax_pane_color = (1, 1, 1, 1.0)
ax.w_xaxis.set_pane_color(ax_pane_color)
ax.w_yaxis.set_pane_color(ax_pane_color)
ax.w_zaxis.set_pane_color(ax_pane_color)
ax.w_xaxis.set_ticks_position('both') # 2nd try
plt.show()

twinx kills tick label color

I am plotting a double plot with two y-axes. The second axis ax2 is created by twinx. The problem is that the coloring of the second y-axis via yticks is not working anymore. Instead I have to set_color the labels individually. Here is the relevant code:
fig = plt.figure()
fill_between(data[:,0], 0, (data[:,2]), color='yellow')
yticks(arange(0.2,1.2,0.2), ['.2', '.4', '.6', '.8', ' 1'], color='yellow')
ax2 = twinx()
ax2.plot(data[:,0], (data[:,1]), 'green')
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
# color='green' has no effect here ?!
# instead this is needed:
for t in ax2.yaxis.get_ticklabels(): t.set_color('green')
show()
Resulting in:
This issue only occurs if I set the tick strings.
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
Omit it, like here
yticks(arange(0.1,0.6,0.1), color='green')
and the coloring works fine.
Is that a bug (could not find any reports to this), a feature (?!) or
am I missing something here? I am using python 2.6.5 with matplotlib 0.99.1.1 on ubuntu.
For whatever it's worth, you code works fine on my system even without the for loop to set the label colors. Just as a reference, here's a stand-alone example trying to follow essentially exactly what you posted:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
num = 200
x = np.linspace(501, 1200, num)
yellow_data, green_data = np.random.random((2,num))
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the yellow data
plt.fill_between(x, yellow_data, 0, color='yellow')
plt.yticks([0.0, 0.5, 1.0], color='yellow')
# Plot the green data
ax2 = plt.twinx()
ax2.plot(x, green_data, 'g-')
plt.yticks([-4, -3, -2, -1, 0, 1], color='green')
plt.show()
My guess is that your problem is mostly coming from mixing up references to different objects. I'm guessing that your code is a bit more complex, and that when you call plt.yticks, ax2 is not the current axis. You can test that idea by explicitly calling sca(ax2) (set the current axis to ax2) before calling yticks and see if that changes things.
Generally speaking, it's best to stick to either entirely the matlab-ish state machine interface or the OO interface, and don't mix them too much. (Personally, I prefer just sticking to the OO interface. Use pyplot to set up figure objects and for show, and use the axes methods otherwise. To each his own, though.)
At any rate, with matplotlib >= 1.0, the tick_params function makes this a bit more convenient. (I'm also using plt.subplots here, which is only in >= 1.0, as well.)
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
ax1.tick_params(axis='y', labelcolor='yellow')
ax2.tick_params(axis='y', labelcolor='green')
plt.show()
The equivalent code for older versions of matplotlib would look more like this:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
for ax, color in zip([ax1, ax2], ['yellow', 'green']):
for label in ax.yaxis.get_ticklabels():
label.set_color(color)
plt.show()

Categories

Resources