How do I create a Matplotlib figure with images and profiles that fit together?

I would like to plot a 2d data as an image, with profiles along the x and y axis displayed below and sideways. This is a fairly common way of displaying data, so there might be an easier way to approach this. I would like to find the simplest and most reliable way that does it correctly and does not use anything outside of matplotlib (although I would be interested to know other packages that might be especially important). In particular, the method should work without changing anything if the shape (aspect ratio) of the data changes.

My main problem is to draw the side plots correctly so that their borders match the main plot.

Sample code:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# generate grid and test data
x, y = np.linspace(-3,3,300), np.linspace(-1,1,100)
X, Y = np.meshgrid(x,y)
def f(x,y) :
    return np.exp(-(x**2/4+y**2)/.2)*np.cos((x**2+y**2)*10)**2
data = f(X,Y)

# 2d image plot with profiles
h, w = data.shape
gs = gridspec.GridSpec(2, 2,width_ratios=[w,w*.2], height_ratios=[h,h*.2])
ax = [plt.subplot(gs[0]),plt.subplot(gs[1]),plt.subplot(gs[2])]
bounds = [x.min(),x.max(),y.min(),y.max()]
ax[0].imshow(data, cmap='gray', extent = bounds, origin='lower')
ax[1].plot(data[:,w/2],Y[:,w/2],'.',data[:,w/2],Y[:,w/2])
ax[1].axis([data[:,w/2].max(), data[:,w/2].min(), Y.min(), Y.max()])
ax[2].plot(X[h/2,:],data[h/2,:],'.',X[h/2,:],data[h/2,:])
plt.show()

      

As you can see from the result below, the way things are scaled, the image on the right does not match the borders correctly

Partial solutions:

1) Manually play with the size of the figure to find the correct aspect ratio to display correctly (could the image ratio + padding + used width ratios be used automatically?). It seems sticky when there are so many packaging options that should take care of these things automatically. EDIT: plt.gcf().set_figheight(f.get_figwidth()*h/w)

seems to work if padding doesn't change.

2) Add ax[0].set_aspect('auto')

, which then flattens the borders, but the image no longer has the correct aspect ratio.

Result from the above code example: Output example

+3


source to share


2 answers


you can use sharex

, and sharey

for this, replace the line ax=

to this:

ax = [plt.subplot(gs[0]),]
ax.append(plt.subplot(gs[1], sharey=ax[0]))
ax.append(plt.subplot(gs[2], sharex=ax[0]))

      



enter image description here

+3


source


I have not been able to create your layout using subplot

and gridspec

while maintaining (1) the ratio of the axes and (2) the limits imposed on the axis. An alternative solution would be to place your axes manually on your shape instead and control the size of the shape accordingly (as you mentioned in your OP). While it requires more work than it does using subplot

and gridspec

, this approach remains fairly simple and can be very powerful and flexible for creating complex layouts where fine control over margins and axis placement is required.

Below is an example showing how this can be achieved by setting the size of the shape to the size specified for the axes. On the contrary, it would also be possible to set the axes within the shape of a predefined size. Then the aspect ratio of the axes will be saved using the picture fields as a buffer.

import numpy as np
import matplotlib.pyplot as plt

plt.close('all')

#------------------------------------------------------------ generate data ----

# generate grid and test data
x, y = np.linspace(-3, 3, 300), np.linspace(-1, 1, 100)
X, Y = np.meshgrid(x,y)
def f(x,y) :
    return np.exp(-(x**2/4+y**2)/.2)*np.cos((x**2+y**2)*10)**2
data = f(X,Y)

# 2d image plot with profiles
h, w = data.shape
data_ratio = h / float(w)

#------------------------------------------------------------ create figure ----

#--- define axes lenght in inches ----

width_ax0 = 8.
width_ax1 = 2.
height_ax2 = 2.

height_ax0 = width_ax0 * data_ratio

#---- define margins size in inches ----

left_margin  = 0.65
right_margin = 0.2
bottom_margin = 0.5
top_margin = 0.25
inter_margin = 0.5

#--- calculate total figure size in inches ----

fwidth = left_margin + right_margin + inter_margin + width_ax0 + width_ax1
fheight = bottom_margin + top_margin + inter_margin + height_ax0 + height_ax2

fig = plt.figure(figsize=(fwidth, fheight))
fig.patch.set_facecolor('white')

#---------------------------------------------------------------- create axe----

ax0 = fig.add_axes([left_margin / fwidth,
                    (bottom_margin + inter_margin + height_ax2) / fheight,
                    width_ax0 / fwidth, height_ax0 / fheight])

ax1 = fig.add_axes([(left_margin + width_ax0 + inter_margin) / fwidth,
                    (bottom_margin + inter_margin + height_ax2) / fheight,
                     width_ax1 / fwidth, height_ax0 / fheight])

ax2 = fig.add_axes([left_margin / fwidth, bottom_margin / fheight,
                    width_ax0 / fwidth, height_ax2 / fheight])

#---------------------------------------------------------------- plot data ----

bounds = [x.min(),x.max(),y.min(),y.max()]
ax0.imshow(data, cmap='gray', extent = bounds, origin='lower')
ax1.plot(data[:,w/2],Y[:,w/2],'.',data[:,w/2],Y[:,w/2])
ax1.invert_xaxis()
ax2.plot(X[h/2,:], data[h/2,:], '.', X[h/2,:], data[h/2,:])

plt.show(block=False)
fig.savefig('subplot_layout.png')

      



Result:

enter image description here

+1


source







All Articles